3. 快速排序

Posted datamining-bio

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了3. 快速排序相关的知识,希望对你有一定的参考价值。

  快速排序(Quick Sort)与冒泡排序均为交换类排序。快排是对冒泡排序的一种改进。由于关键字的比较和交换是跳跃进行的,因此,快速排序是一种不稳定的排序方法。

 

0. 序

1. 冒泡排序

2. 快速排序

2.1 基本思想

2.2 一趟快速排序(一趟划分)

2.3 过程

2.4 实现

2.5 复杂度分析

2.5.1 时间复杂度(包含证明)

2.5.2 空间复杂度

 

       0. 序

  快速排序算法最早由图灵奖获得者Tony Hoare设计出来的,他在形式化方法理论以及ALGOL60编程语言的发明中都有卓越的贡献,是上世纪最伟大的计算机科学家之一。

  快速排序被列为20世纪十大算法之一。

 

  1. 冒泡排序

 

  假设在排序过程中,记录序列R[1...n] 的状态为:

技术图片

 

 

 

  更多的冒泡排序讲解见https://www.cnblogs.com/datamining-bio/p/9715774.html

  2. 快速排序

  快速排序也是通过不断比较和移动交换来实现排序的,只不过它的实现,增大了记录的比较和移动的距离,将关键字较大的记录从前面直接移动到后面,关键字较小的记录从后面直接移动到前面,从而减少了总的比较次数和移动交换次数

  2.1 基本思想

  通过一趟排序将待排记录分割成独立的两部分,其中一部分记录的关键字均比另一部分记录的关键字小,然后分别对这两部分继续进行排序,直到整个序列有序。

  2.2 一趟快速排序(一次划分)

  目标:找一个记录,以它的关键字作为“枢纽”,凡其关键字小于枢纽的记录均移动至该记录之前,反之,凡关键字大于枢纽的记录均移动至该记录之后。

技术图片

 

技术图片

 

 

 技术图片

 

 

 

  设 R[s] = 52 为枢纽

  将R[high].key 和枢纽的关键字进行比较,要求R[high].key ≥ 枢纽的关键字

  将R[low].key 和枢纽的关键字进行比价,要求R[low].key ≤ 枢纽的关键字

 

  可见,经过“一次划分”,将关键字序列:

  52,  498036145861972375

  调整为: 23491436, (52),58,  6197,  8075

  在调整过程中,设立了两个指针:low 和 high,它们的初值分别为:s 和 

  之后逐渐减小high,增加low,并保证 R[high].key 52,和 R[low].key 52,否则进行记录的“交换”。

 

  2.3 过程

 

  首先对无序的记录序列进行“一次划分”,之后分别对分割所得两个子序列“递归”进行快速排序。

技术图片

 

 

   2.4 实现

// 快速排序  O(nlogn) --->逆序O(n^2)
// 调用QSort(1, n)   待排记录index = 1 ... n
public int[] QSort(int low, int high){
    if(low < high){
        int p = Partition(low, high);
        QSort(low, p-1);
        QSort(p+1, high);
    }
    return needSort;
}
private int Partition(int low, int high){
    needSort[0] = needSort[low];
    while(low < high){
        while(low < high && needSort[high] >= needSort[0]) high--;
        needSort[low] = needSort[high];
        while(low < high && needSort[low] <= needSort[0]) low++;
        needSort[high] = needSort[low];
    }
    needSort[low] = needSort[0];
    return low;
}

 

  2.5  复杂度分析

  2.5.1 时间复杂度

  ① 在最优情况下,Partition每次都划分得很均匀,如果排序n个关键字,其递归树(见2.5.2)的深度就为技术图片,即仅需递归log2n次,需要时间为T(n)的话,第一次Partition应该是需要对整个数组扫描一遍,做n次比较。然后,获得的枢纽将数组一分为二,那么各自还需要T(n/2)的时间(注意是最好情况,所以平分两半)。于是不断地划分下去,我们就有了下面的不等式推断。 

T(n) 2T(n/2) + n, T(1) = 0

T(n) 2( 2T(n/4) + n/2 ) + n = 4T(n/4) + 2n

T(n) 4( 2T(n/8) + n/4 ) + 2n = 8T(n/8) + 3n

...

T(n) nT(1) + (log2n) * n = O(nlogn)

  

  不知道为什么是“≤”而不是“=”? 待补充

 

  也就是说,在最优的情况下,快速排序算法的时间复杂度为O(nlogn)。

 

 

 

  ② 在最坏情况下,待排序的序列为正序或逆序,每次划分只得到一个比上一次划分少一个记录的子序列,注意另一个为空。如果递归树画出来,它是一颗斜树。此时需要执行n-1次递归调用,且第i次划分需要经过n-i次关键字的比较才能找到第i个记录,也就是枢纽的位置,因此比较次数为技术图片,最终其时间复杂度为O(n2)

 

 

 

  ③ 平均的情况,设枢纽的关键字应该在第k的位置(1kn),每个位置的概率相同,均为1/n,那么:

 

技术图片

 

 

  数学归纳法可证明,其数量级为O(nlogn)。 待补充

 

  通常,快速排序被认为是在所有同数量级O(nlogn) 的排序方法中,其平均性能是最好的。但是,若待排记录的初始状态为按关键字有序或基本有序时,快速排序将蜕化为冒泡排序,其时间复杂度为O(n2)

 

  为避免出现这种情况,需在进行一次划分之前,进行“预处理”,即:先对R(s),keyR(t).keyR[└(s+t)/2┘].key,进行相互比较,然后取关键字为“三者之中”的记录为枢纽记录。

 

 

  2.5.2 空间效率

 

  举例:

技术图片

 

 

  快速排序的递归过程可用生成一颗二叉树形象地给出,下图对应上面例子递归调用过程的二叉树。

技术图片

 

 

  快速排序是递归的,每层递归调用时的指针和参数均要用栈来存放,递归调用层数与上述二叉树的深度一致。因而,存储开销在理想情况下为O(log2n),即树的高度,其空间复杂度也就为O(logn);在最坏情况下,需要进行n-1次递归调用,即二叉树是一个单链,为O(n);平均情况,空间复杂度也为O(logn)

  注意,由于关键字的比较和交换是跳跃进行的,因此,快速排序是一种不稳定的排序方法。

 

参考:

  上课ppt

  《大话数据结构》程杰著  清华大学出版社,2011.6(2017.6 重印)

 

 

 

 

 

以上是关于3. 快速排序的主要内容,如果未能解决你的问题,请参考以下文章

初识Spring源码 -- doResolveDependency | findAutowireCandidates | @Order@Priority调用排序 | @Autowired注入(代码片段

Xcode 快速开发 代码块

快速排序/快速选择算法

Xcode 快速开发 代码块 快捷键

c#代码片段快速构建代码

冒泡排序与快速排序