排序算法之快速排序

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



平均时间复杂度 最优时间复杂度 最差时间复杂度 空间复杂度 稳定性
快速排序 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]
  • 优化
    • 最理想的状况是,选择的基准数刚好能把待排序列分红两个几乎等长的区间
    • 而上面的方法是以待排序列的第一个数为基准数
    • 咱们能够改变基准数的选择方案来优化这个算法
    • 随机选取基准:取待排序列中任意一个元素做为基准
    • 三数取中:使用左端、右端和中心位置上的三个元素的中值做为枢纽元
    • 实现方法:按照上面的算法,只要让选择的基准和待排序列的第一个数字交换便可
相关文章
相关标签/搜索