深入解析:排序算法大全

0. 前言

以下代码均通过了这道题 912. 排序数组 - 力扣(LeetCode)  ,代码质量是可以保证的。

在具体讲解算法之前,我们先补充几个小知识!

1. 时间复杂度(《大话数据结构》 这本书中的解释)

2. 空间复杂度

描述算法所需额外空间随输入数据规模增长的变化趋势(额外空间 = 算法执行过程中临时占用的空间,不包括输入数据本身的空间)。

简单来说就是:当处理的数据量变大时,算法需要额外占用多少内存?

举个例子:原地排序(冒泡排序、插入排序)、遍历数组时只使用几个临时变量(比如计算数组总和)、修改数据时直接在原对象上操作。 这个空间复杂度为O(1)

3. 稳定性

仅针对排序算法,指:排序后,相等元素的相对顺序保持不变

比如数组 [3, 2, 2, 1],排序后若为 [1, 2, 2, 3],且原来两个 2 的位置没有交换,则是稳定排序;若变成 [1, 2(原来的第二个2), 2(原来的第一个2), 3],则是不稳定排序。

1.冒泡排序

核心思路:

冒泡排序:像在海里的气泡一样,最底下的最小,越往上越大。在这里我们比较左右两数字(也可以说是上、下两个气泡),大的在右(上),小的在左(下),不对的我们交换一下。

在每一轮的交换结束都可以确定一个数的位置。

    //1.冒泡排序
    public int[] bubbleSort(int[] arr) {
        int n = arr.length;
        for (int i = 0; i < n - 1; i++) { // 比较的趟数  每一趟确定一个最大的数
            int check = 0;
            for (int j = 0; j < n - i - 1; j++) {//每一趟固定无序数中的一个最大的数
                if (arr[j] > arr[j + 1]) { // 交换
                    int temp = arr[j];
                    arr[j] = arr[j + 1];
                    arr[j + 1] = temp;
                    check = 1;
                }
            }
            if (check == 0) {// 如果check = 0, 说明数组已经有序!
                break;
            }
        }
        return arr;
    }

2.插入排序

核心思路:扑克牌我们都玩过,玩家轮流从牌堆里取一张牌,手里的牌是有序的,现在插入一张,使整体仍然有序。

/**
 * 插入排序
 */
public int[] insertionSort(int[] arr) {
        int n = arr.length;
        for (int i = 1; i < n; i++) {
            int temp = arr[i];
            int index = i;
            //找到要插入的位置
            for(int j = i; j > 0; j--) {
                if(arr[j - 1] < temp) {
                    break;
                }
                index--;
            }
            //后移,腾出位置
            for(int j = i; j > index;j--){
                arr[j] = arr[j-1];
            }
            // 插入
            arr[index] = temp;
        }
        return arr;
    }

如下对找位置和插入进行优化:

    //2.插入排序
    /* 改进
     * 插入排序是在有序数组中,再插入一个
    * */
    public int[] insertionSort1(int[] arr) {
        int n = arr.length;
        for (int i = 1; i < n; i++) {
            int tep = arr[i];
            for(int j = i; j > 0; j--) {
                if(tep >= arr[j-1]) {
                    arr[j] = tep;      // 插入
                    break;
                }else {
                    arr[j] = arr[j-1]; //后移
                }
            }
            if(tep < arr[0]) arr[0] = tep;  // 0 位置特殊处理
        }
        return arr;
    }

3.选择排序

核心思路:我们开启了上帝视角,每次从一堆无序数中选出一个最小的加入到有序数中,当无序数堆没有了,我们便获取了一组有序数。

    //选择排序
    public int[] selectionSort(int[] arr) {
        int n = arr.length;
        for (int i = 0; i < n - 1; i++) { //选择的趟数
            int tep = arr[i];
            int index = i;
            for (int j = i + 1; j < n; j++) {
                if(arr[j] < tep) {
                    index = j;
                    tep = arr[j];
                }
            }
            if(index != i) {
                arr[index] = arr[i];
                arr[i] = tep;
            }
        }
        return arr;
    }

4.希尔排序

核心思路:分组排序,组内插入排序。

     /**
     * 希尔排序
     * 对于希尔排序我更喜欢称它为:分组排序,组内插入排序
     */
    public int[] shellSort(int[] arr) {
        int n = arr.length;
        int interval = n;       //  间隔 等于 分组数
        int stand = 2;
        do {
            interval = interval / stand;
            for (int i = 0; i < interval;i++){// 分组
                for (int j = i + interval;j < n;j += interval){ //组内排序
                    // 组内插入排序
                    int tep = arr[j];
                    for (int k = j;k - interval >= 0;k -= interval){
                        if(arr[k - interval] < tep){
                            arr[k] = tep;               // 插入
                            break;
                        }else{
                            arr[k] = arr[k - interval]; // 后移
                        }
                    }
                    if(tep < arr[i]) arr[i] = tep;      // 每组第一个位置特殊处理
                }
            }
        }while (interval > 1);
        return arr;
    }

5.堆排序

利用堆这种数据结构。

  • 大顶堆:每个父节点的值 ≥ 其左右子节点的值 → 堆顶(根节点)是整个堆的最大值
  • 小顶堆:每个父节点的值 ≤ 其左右子节点的值 → 堆顶是整个堆的最小值
    /**
     *  堆排序
     */
    public int[] heapSort(int[] arr) {
        int n = arr.length;
        PriorityQueue pq = new PriorityQueue<>();
        for (int i = 0; i < n; i++) {
            pq.offer(arr[i]);
        }
        for (int i = 0; i < n; i++) {
            arr[i] = pq.poll();
        }
        return arr;
    }

6.归并排序

核心思路:两个基本的操作,一个是分,也就是把原数组划分成两个子数组的过程。另一个是治,它将两个有序数组合并成一个更大的有序数组。

  • 将待排序的线性表不断地切分成若干个子表,直到每个子表只包含一个元素,这时,可以认为只包含一个元素的子表是有序表。
  • 将子表两两合并,每合并一次,就会产生一个新的且更长的有序表,重复这一步骤,直到最后只剩下一个子表,这个子表就是排好序的线性表。

     /**
     * 归并排序
     * 准确的说是:二路归并
     */
    public int[] mergeSort(int[] arr) {
        int n = arr.length;
        mSort(0,n-1,arr);
        return arr;
    }
    private void mSort(int l, int r, int[] arr) {
        if(l >= r) return;
        int mid = (l+r)/2;
        mSort(l,mid,arr);
        mSort(mid+1,r,arr);
        merge(l,mid,r,arr);
    }
    private void merge(int l, int mid, int r, int[] arr) {
        int[] array = new int[r-l+1];
        int index = 0;
        int i = l,j = mid + 1;
        for(;i <= mid && j <= r;){
            if(arr[i] < arr[j]){
                array[index++] = arr[i++];
            }else {
                array[index++] = arr[j++];
            }
        }
        for(;i <= mid;i++){
            array[index++] = arr[i];
        }
        for(;j <= r;j++){
            array[index++] = arr[j];
        }
        for (int ii = 0; ii < array.length; ii++ ) {
            arr[l++] = array[ii];
        }
    }

7.快速排序

核心思路:选择一个基准元素(base),通常选择第一个或最后一个元素,然后对数组进行分区操作,使得:

  • 比基准元素小的元素都移到基准的左边

  • 比基准元素大的元素都移到基准的右边 这个过程称为一趟快速排序。

快速排序算法—图文详解,一篇就够了!-CSDN博客    (过程参考这个)

    /**
     * 快速排序
     * 三数取中版
     */
    //    TODO 第一版
    public int[] quickSort(int[] arr) {
        int n = arr.length - 1;
        qSort(0,n,arr);
        return arr;
    }
    private void qSort(int l, int r, int[] arr) {
        if(l >= r) return;
        int mid = l + (r - l) / 2;
        int standardIndex = findMiddle(l,mid,r,arr);
        int standard = arr[standardIndex];
        swap(r,standardIndex,arr);
        int i = l,k = r;
        while(i < k){
            while (i < k && arr[i] < standard){
                i++;
            }
            while (i < k && arr[k] >= standard){
                k--;
            }
            swap(i,k,arr);
        }
        swap(i,r,arr);
        qSort(l,i - 1,arr);
        qSort(i + 1,r,arr);
    }
    private void swap(int l, int mid, int[] arr) {
        int tep = arr[l];
        arr[l] = arr[mid];
        arr[mid] = tep;
    }
    private int findMiddle(int l, int mid, int r, int[] arr) {
        if(arr[l] >= arr[r]){
            if (arr[mid] > arr[l]) return l;
            if(arr[r] > arr[mid]) return r;
            return mid;
        }else {// r > l
            if(arr[mid] > arr[r]) return r;
            if(arr[l] >= arr[mid]) return l;
            return mid;
        }
    }
    private int findMiddle(int l, int mid, int r, int[] arr) {
        if((arr[l] - arr[mid]) * (arr[l] - arr[r]) <= 0) return l;
        if((arr[mid] - arr[l]) * (arr[mid] - arr[r]) <= 0) return mid;
        return r;
    }

   //TODO: 第二版
    /**
     *  对于快速排序中:挖坑法、前后指针法、左右指针法  使用 三数取中,达到一定的优化
     *  但仍然多多少少还有一些瑕疵, 1,2,6,8,5,5,5,5,5,5,1,1,1
     *  向这些有重复的,便会多次的被排序
     *  这里采用三路划分 (效率最高)
     */
    public int[] quickSort(int[] arr) {
        int n = arr.length - 1;
        qSort(0,n,arr);
        return arr;
    }
    private void qSort(int l, int r, int[] arr) {
        if(l >= r) return;
        int standard = arr[l + new Random().nextInt(r - l + 1)];
        int left = l - 1;
        int right = r + 1;
        for (int i = l; i < right; ) {
            if(arr[i] < standard) {
                swap(++left,i++,arr);
            }else if(arr[i] == standard){
                i++;
            }else{
                swap(--right,i,arr);
            }
        }
        qSort(l,left,arr);
        qSort(right,r,arr);
    }
    private void swap(int l, int mid, int[] arr) {
        int tep = arr[l];
        arr[l] = arr[mid];
        arr[mid] = tep;
    }

如上便是今天的分享了!      

posted @ 2025-12-28 08:03  clnchanpin  阅读(60)  评论(0)    收藏  举报