排序算法之快速排序

这里是传送门⇒总结:关于排序算法



平均时间复杂度 最优时间复杂度 最差时间复杂度 空间复杂度 稳定性
快速排序 O(nlogn) O(nlogn) O(n2) O(logn) 不稳定


快速排序是对冒泡排序的一种改进,是一种划分交换排序,采用了分治的思想

  • 算法描述
    • 从待排序列中选定一个基准数(比如序列的第一个数),把比基准数小的数放在左边,比基准数大的放在右边,一样大的放哪边都行(以下是让其在左边)。这个过程称为分区
    • 然后再按照这种方式把左右两个区间再分别进行分区
    • 以此类推...
    • 整个排序过程可以递归进行(好像说递归有可能会爆栈)
  • 分区的具体实现
    • 总结起来就是“挖坑填数”
    • 选定待排序列第一个数array[left]为基准数,把array[left]的值保存在变量baseVal中
    • 由于array[left]的值已经被保存起来了,可以理解为array[left]的值被拿走了,现在array[left]上有一个坑,现在来找一个数填进来
    • 这个坑在左边,所以从后向前扫描待排序列array,用变量j记录扫描位置
    • 当发现此时的array[j]的值比基准值小或者等于基准数,把此时的array[j]的值填到坑里,也就是让a[left] = a[j]
    • 现在的a[left]填完数了,可是a[j]的数被拿走填到a[left]了,现在变成a[j]有坑了,需要找另一个数来填
    • 这个时候坑在右边了,从头向后扫描待排序列array,用变量i记录扫描位置
    • 当发现此时的array[i]的值比基准值大,把此时的array[i]的值填到坑里,也就是让a[j] = a[i]
    • 现在的a[j]填完数了,可是a[i]的数被拿走填到a[j]了,现在变成a[i]有坑了,需要找另一个数来填
    • 以此类推...
    • 直到i和j相遇了,即左右两边的坑都填完了,只剩下中间的坑
    • 最后把一开始保存在baseVal的值填进最后一个坑
    • 也就是在左边挖坑,去右边找一个应该在左边的数,填进坑里,这个行为导致右边有坑,所以得去左边找一个应该在右边的数来填,然后又导致左边有坑...最后剩一个坑,就把一开始保存的基准数填进去
  • JS实现
// 此处传入的array会被直接改变
function QuickSort(array, left, right) {
    if (left < right) {
        var baseVal = array[left];
        var i = left,
            j = right;
        while (i < j) {
            while (i < j && array[j] > baseVal) {
                j--;
            }
            if (i < j) {
                array[i++] = array[j];
            }
            while (i < j && array[i] <= baseVal) {
                i++;
            }
            if (i < j) {
                array[j--] = array[i];
            }
        }
        array[i] = baseVal;
        QuickSort(array, left, i - 1);
        QuickSort(array, i + 1, right);
    }
}
  • 分析
    • 快速排序的一次分区是从两头交替寻找合适的数,直到i,j相遇,所以每一次分区的时间复杂度都是O(n),那么整个快速排序的时间复杂度与分区次数有关
    • 最差情况是每次分区选择的基准数都是当前序列的最小值(即待排序列一开始就升序)的时候,这会导致每次分区后左区间的长度为0,(其实就相当于冒泡了,每次只排好一个数)这种情况下,长度为n的待排序列将进行n-1次分区,那么最差时间复杂度为O(n2)
    • 最优情况是每次分区选择的基准数恰好将当前序列几乎等分,这种情况下,长度为n的待排序列将进行大概log2n次分区,那么最优时间复杂度为O(nlogn)
    • 尽管快速排序只需要O(1)大小的辅助空间,但考虑递归深度,快速排序需要一个栈空间来实现递归。最好的情况下,所需栈的最大深度为log2(n+1),最优空间复杂度为O(logn);但最坏的情况下,所需栈的最大深度为n,最差空间复杂度为O(n)。快速排序的平均空间复杂度为O(logn)
    • 想知道怎么由递归算法时间复杂度公式计算时间复杂度的,看这个别人家的博客
    • 或者是这种利用递归树来描述递归算法执行情况的分析方式,看另外一个别人家的博客
    • 由于键值的比较和交换是跳跃进行的,因此快速排序是一种不稳定的排序。假如觉得把键值比较条件array[i] <= baseVal改成array[i] < baseVal可以变成稳定排序的话,请看这个例子[3,4,4,2,6]
  • 优化
    • 最理想的情况是,选择的基准数恰好能把待排序列分成两个几乎等长的区间
    • 而上面的方法是以待排序列的第一个数为基准数
    • 我们可以改变基准数的选择方案来优化这个算法
    • 随机选取基准:取待排序列中任意一个元素作为基准
    • 三数取中:使用左端、右端和中心位置上的三个元素的中值作为枢纽元
    • 实现方法:按照上面的算法,只要让选择的基准和待排序列的第一个数字交换即可
posted @ 2021-02-23 21:45  有机物与鱼  阅读(179)  评论(0编辑  收藏  举报