排序算法

90 阅读4分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

1. 三种低级排序

冒泡排序
选择排序
插入排序
(希尔排序)

2. 三种高级排序

  • 快速排序 时间:o(nlogn) ~ o(n^2) 空间:o(logn)

分而治之,选取一个支点pivot(具体值),先整体有序,再局部有序 当一个序列中存在大量重复的元素,复杂度会趋近于O(n^2)

  • 归并排序 时间:o(nlogn) 空间:o(n) 分而治之,,选取一个支点pivot(数组下标),先局部有序,再整体有序

相同点:

两个排序的基本思想都是分治--分而治之;具体实现都用递归。

不同点:

1、快速排序:边分解边排序。每次分解都实现整体上有序,即参照值左侧的数都小于参照值,右侧的大于参照值;是自上而下的排序; 归并排序:先分解再合并。先递归分解到最小区间,然后从小去区间开始合并排序,是自下而上的归并排序; 2、选取分界点时,快速排序选的是值,一半比这个值大,一半比这个值小。 归并排序选的是数组地址,即下标。不用交换处理,直接把数组一切两半。 3、快速排序是原地排序,原地排序指的是空间复杂度为O(1); 归并排序不是原地排序,因为两个有序数组的合并需要额外的空间协助才能合并; 4、快速排序是不稳定的,时间复杂度在O(nlogn)~O(n^2)之间 。归并排序是稳定的,时间复杂度是O(nlogn)。

  • 堆排序 时间:o(nlogn) 空间:o(1)

进行筛选时,有可能把后面相同关键字的元素调整到前面,所以堆排序算法是一种不稳定的排序方法

3. 总结

当数据量,数据规模较大时,应该采用此3类排序算法,这样效率相比于之前的时间复杂度为O(n^2)的三种排序算法来说更高、更好些。 这三类排序算法的结论:

  • 快速排序是目前基于比较的内部排序中被认为是最好的方法,当待排序的关键字是随机分布时,快速排序的平均时间最短;
  • 堆排序所需的辅助空间少于快速排序,并且不会出现快速排序可能出现的最坏情况,适合超大数据量。这两种排序都是不稳定的。
  • 若要求排序稳定,则可选用归并排序。

三种常用的高级排序算法

方法一:快速排序

复杂度分析

  • 时间复杂度:最坏是O(n^2),最好是O(nlogn),平均复杂度O(nlogn)
  • 空间复杂度:因为没有额外的空间开销,O(1)
  • 算法稳定性:快速排序是不稳定的排序算法。因为我们无法保证相等的数据按顺序被扫描到和按顺序存放。 当一个序列中存在大量重复的元素,复杂度会趋近于O(n^2)
public void quickSort(int[] nums,int low, int high){
    int base = nums[low];
    int start=low;
    int end=high;
    while(end>start){//注意start必须小于end
        while(end>start && nums[end]>=base) end--;//这里是while,而不是if
    
        if(end>start){
            nums[start]=nums[end];
            start++;
            //nums[end]=base;
        }
    
        while(end>start && nums[start]<=base) start++;
    
        if(end>start){
            nums[end]=nums[start];
            end--;
            //nums[start]=base;
        }
    }
    
    //把基数放到start==end的位置
    nums[start]=base;
    
    //对左边进行快排
    if(start>low) quickSort(nums,low,start-1);
    
    //对右边进行快排
    if(end<high) quickSort(nums,end+1,high);
}
    

方法二:归并排序

复杂度分析

  • 时间复杂度:O(nlogn)
  • 空间复杂度:O(n),需要开辟额外的空间。
  • 算法稳定性:归并排序是稳定的排序算法

public void mergeSort(int[] arr, int low, int high){
    if(low<high){
        int mid = (low+high)/2;
        mergeSort(arr,low,mid);
        mergeSort(arr,mid+1,high);
        merge(arr, low, mid, high);
    }
}

private void merge(int[] arr, int low, int mid, int high){
    int left_start = low, left_end = mid;
    int right_start = mid+1, right_end = high;
    
    int[] tmp = new int[high-low+1];
    int i = 0;
    while(left_start<=left_end && right_start<=right_end){
        tmp[i++] = arr[left_start]<=arr[right_start] ? arr[left_start++] : arr[right_start++];
    }
    
    while(left_start<=left_end){
        tmp[i++] = arr[left_start++];
    }
    while(right_start<=right_end){
        tmp[i++]=arr[right_start++];
    }
    for(int j = 0;j<i; j++){
        arr[low+j] = tmp[j];
    }
}
    

方法三:堆排序

复杂度分析

  • 时间复杂度:O(nlogn)
  • 空间复杂度:O(1)。
  • 算法稳定性:不稳定 进行筛选时,有可能把后面相同关键字的元素调整到前面,所以堆排序算法是一种不稳定的排序方法

 public void HeapSort(int[] arr) {
        /**
         * 第一步,初始化堆,最大堆,从小到大。目的是对元素排序
         * i从完全二叉树的第一个非叶子节点开始,也就是len/2-1开始(数组下标从0开始),从下往上调整堆
         */
        for (int i = arr.length / 2 - 1; i >= 0; i--) {
            //从第一个非叶子结点从下至上,从右至左调整结构
            adjustHeap(arr, i, arr.length);
        }
        /**
         * 第二步,交换堆顶(最大)元素和二叉堆的最后一个叶子节点元素。目的是交换元素
         * i从二叉堆的最后一个叶子节点元素开始,也就是len-1开始(数组下标从0开始)
         */
        for (int j = arr.length - 1; j > 0; j--) {
            //将堆顶元素与末尾元素进行交换
            int temp = arr[0];
            arr[0] = arr[j];
            arr[j] = temp;
            //交换完之后需要重新调整二叉堆,从头开始调整,此时Index=0
            adjustHeap(arr, 0, j);
        }

    }

    /**
     * 调整大顶堆(仅是调整过程,建立在大顶堆已构建的基础上)
     * 从上往下,从左往右调整
     * @param arr
     * @param index
     * @param length
     */
    public void adjustHeap(int[] arr, int index, int length) {//注意arr是数组
        int temp = arr[index];//先取出当前元素i
        int l_leaf = 2 * index + 1;
        for (int i = l_leaf; i < length; i = 2 * i + 1) {//总是从结点的左子结点开始
            int r_leaf = i + 1;
            //如果左子结点小于右子结点,i指向右子结点
            i = (r_leaf < length && arr[i] < arr[r_leaf]) ? r_leaf : i;
            if (arr[i] > temp) {//如果子节点大于父节点,将子节点值赋给父节点(不用进行交换)
                arr[index] = arr[i];//父节点已经调整完毕,不再变动,可以赋值
                index = i;// 可能需要继续往下调整堆,只记录索引
            } else {
                break;
            }
        }
        arr[index] = temp;//将temp值放到最终的位置
    }
    

参考
全面图解快速排序
全面图解堆排序
全面图解归并排序