zoukankan      html  css  js  c++  java
  • 排序之快速排序详解

    一、算法介绍

    快速排序(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 , 这也要加进去, 所以一共是:

    四、完整代码示例

    public class QuickSort {
    
        //任取一个元素 (如第一个) 为中心
        //所有比它小的元素一律前放,比它大的元素一律后放,形成左右两个子表;
        //对各子表重新选择中心元素并依此规则调整,直到每个子表的元素只剩一个
        //一趟排序过程后我们返回枢纽的位置
        int partition(int A[], int left, int right) {
            //选择枢纽元素
            int p = A[left];
            while (left < right) {
                //如果尾指针位置的数比枢纽数要大,移动尾指针的位置,否则就把所指示的值给首指针的位置
                while (left < right && A[right] >= p) {
                    --right;
                }
                A[left] = A[right];
                //如果首指针位置的数比枢纽数要小,移动首指针的位置,否则就把所指示的值给尾指针的位置
                while (left < right && A[left] <= p) {
                    ++left;
                }
                A[right] = A[left];
            }
            //此时的首尾指针已经相等,把枢纽的值赋给首尾指针相等的位置即可
            A[left] = p;
            return left;
        }
    
        //快速排序的递归
        void Quick(int A[], int left, int right) {
            //定义一个枢纽的位置
            int pnode;
            if (left < right) {
                pnode = partition(A, left, right);
                Quick(A, left, pnode - 1);
                Quick(A, pnode + 1, right);
            }
        }
    
        public static void main(String[] args) {
    
        }
    

    参考文章

    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


    欢迎关注个人技术公众号:Coder辰砂

  • 相关阅读:
    host文件的用处
    探究 Flex 组件的生命周期
    QQ在开发中的应用
    转:linux进程间通信的几种机制的比较及适用场合
    转:Linux 2.4.x内核软中断机制
    转:PLL 锁相环
    转:LPC2214的PLL与定时器设置
    转: V4L2驱动程序架构
    转:linux select 多路复用机制
    [转] Linux 句柄是什么 ?
  • 原文地址:https://www.cnblogs.com/tojian/p/10099502.html
Copyright © 2011-2022 走看看