排序之快速排序详解

Posted Coder辰砂

tags:

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

一、算法介绍

快速排序(Quick Sort):它的基本思想是,通过一趟排序将待排记录分割成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,分别对这丙部分继续进行快速排序,直至整个序列有序。

任取一个元素 (如第一个) 为中心 所有比它小的元素一律前放,比它大的元素一律后放,形成左右两个子表; 对各子表重新选择中心元素并依此规则调整,直到每个子表的元素只剩一个

①每一趟的子表的形成是采用从两头向中间交替式逼近法;

②由于每趟中对各子表的操作都相似,可采用递归算法。

二、基本步骤

设置两个指针i,j,首先在序列里面选出一个枢纽temp出来,将j指向的数字和temp比较,如果比temp大,则减1,如果比temp小,应该把当前j指向的位置上面的数值和排序之快速排序详解

三、算法分析

最好:划分后,左侧右侧子序列的长度相同,

最坏:从小到大排好序,递归树成为单支树,每次划分只得到一个比上一次少一个对象的子序列,必须经过 n-1 趟才能把所有对象定位,而且第 i 趟需要经过 n-i 次关键码比较才能找到第 i 个对象的安放位置

排序之快速排序详解

若出现各种可能排列的概率相同,则可取最好情况和最坏情况的平均情况

时间效率:O(nlog2n) —每趟确定的元素呈指数增加 空间效率:O(log2n)—递归要用到栈空间 稳 定 性: 不稳定 —可选任一元素为支点

1.如何选枢纽

由上述描述可以知道,快速排序是以枢纽的点进行来回交换,所以快速排序的排序趟数和初始的序列有关系。 所以选择快速排序的枢纽点是非常重要的,因为关系到排序的效率。

取前或后法:序列中的第一个或最后一个元素作为基准,如果输入序列(上文中的数组)是随机的,处理时间可以接受的。如果数组已经有序时,此时的分割就是一个非常不好的分割。因为每次划分只能使待排序序列减一,此时为最坏情况,时间复杂度为Θ(n^2)。而且,输入的数据是有序或部分有序的情况是相当常见的。因此,使用第一个元素作为枢纽元是非常糟糕的

随机选取基准: 这是一种相对安全的策略。由于枢轴的位置是随机的,那么产生的分割也不会总是会出现劣质的分割。在整个数组数字全相等时,仍然是最坏情况,时间复杂度是O(n2)。所以随机化快速排序可以对于绝大多数输入数据达到O(nlogn)的期望时间复杂度。

三数取中法:在快排的过程中,每一次我们要取一个元素作为枢纽值,以这个数字来将序列划分为两部分。在此我们采用三数取中法,也就是取左端、中间、右端三个数,然后进行排序,将中间数作为枢纽值。显然使用三数中值分割法消除了预排序输入的不好情形,并且减少快排大约14%的比较次数。

2.如何证明时间复杂度

1、最优情况

在最优情况下,Partition每次都划分得很均匀,如果排序n个关键字,其递归树的深度就为 [log2n]+1( [x] 表示不大于 x 的最大整数),即仅需递归 log2n 次,需要时间为T(n)的话,第一次Partiation应该是需要对整个数组扫描一遍,做n次比较。然后,获得的枢轴将数组一分为二,那么各自还需要T(n/2)的时间(注意是最好情况,所以平分两半)。于是不断地划分下去,就有了下面的不等式推断: 这说明,在最优的情况下,快速排序算法的时间复杂度为O(nlogn)。

2.最坏情况

然后再来看最糟糕情况下的快排,当待排序的序列为正序或逆序排列时,且每次划分只得到一个比上一次划分少一个记录的子序列,注意另一个为空。如果递归树画出来,它就是一棵斜树。此时需要执行n‐1次递归调用,且第i次划分需要经过n‐i次关键字的比较才能找到第i个记录,也就是枢轴的位置,因此比较次数为n(n-1)/2,最终其时间复杂度为O(n^2)。

3.平均时间复杂度

直接设对规模的数组排序需要的时间期望为, 期望其实就是平均复杂度换个说法. 空表的时候不用排, 所以初值条件就是 T(0) = 0 .所谓快排就是随便取出一个数,一般是第一个数,然后小于等于他的放左边, 大于他的的排右边.比如左边 k 个那接下来还要排: T(n - k) + T (k - 1) 的时间.然后 k 多少那是不确定的, 遍历 1~ n , 出现概率都是相等的. 另外分割操作本身也要时间 P(n) , 操作花费是线性时间 P(n) = cn , 这也要加进去, 所以一共是:

四、完整代码示例

 
   
   
 
  1. public class QuickSort {


  2.    //任取一个元素 (如第一个) 为中心

  3.    //所有比它小的元素一律前放,比它大的元素一律后放,形成左右两个子表;

  4.    //对各子表重新选择中心元素并依此规则调整,直到每个子表的元素只剩一个

  5.    //一趟排序过程后我们返回枢纽的位置

  6.    int partition(int A[], int left, int right) {

  7.        //选择枢纽元素

  8.        int p = A[left];

  9.        while (left < right) {

  10.            //如果尾指针位置的数比枢纽数要大,移动尾指针的位置,否则就把所指示的值给首指针的位置

  11.            while (left < right && A[right] >= p) {

  12.                --right;

  13.            }

  14.            A[left] = A[right];

  15.            //如果首指针位置的数比枢纽数要小,移动首指针的位置,否则就把所指示的值给尾指针的位置

  16.            while (left < right && A[left] <= p) {

  17.                ++left;

  18.            }

  19.            A[right] = A[left];

  20.        }

  21.        //此时的首尾指针已经相等,把枢纽的值赋给首尾指针相等的位置即可

  22.        A[left] = p;

  23.        return left;

  24.    }


  25.    //快速排序的递归

  26.    void Quick(int A[], int left, int right) {

  27.        //定义一个枢纽的位置

  28.        int pnode;

  29.        if (left < right) {

  30.            pnode = partition(A, left, right);

  31.            Quick(A, left, pnode - 1);

  32.            Quick(A, pnode + 1, right);

  33.        }

  34.    }


  35.    public static void main(String[] args) {


  36.    }

参考文章

https://www.jianshu.com/p/c8b1384238f7

https://www.cnblogs.com/chengxiao/p/6262208.html

https://blog.csdn.net/oohaha_123/article/details/26558363

https://www.zhihu.com/question/22393997/answer/406278523

https://www.cnblogs.com/onepixel/articles/7674659.html



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

排序之快速排序详解

Python实现的数据结构与算法之快速排序详解

Java学习笔记之十一Java中常用的8大排序算法详解总结

算法排序之堆排序

快速排序算法详解及代码实现

快速排序详解