zoukankan      html  css  js  c++  java
  • 算法分析-快速排序QUICK-SORT

    设要排序的数组是A[0]……A[N-1],首先任意选取一个数据(通常选用数组的第一个数)作为关键数据,然后将所有比它小的数都放到它前面,所有比它大的数都放到它后面,这个过程称为一趟快速排序。值得注意的是,快速排序不是一种稳定的排序算法,也就是说,多个相同的值的相对位置也许会在算法结束时产生变动。
    一趟快速排序的算法是:
    1)设置两个变量i、j,排序开始的时候:i=0,j=N-1;
    2)以第一个数组元素作为关键数据,赋值给key,即key=A[0];
    3)从j开始向前搜索,即由后开始向前搜索(j--),找到第一个小于key的值A[j],将A[j]和A[i]互换;
    4)从i开始向后搜索,即由前开始向后搜索(i++),找到第一个大于key的A[i],将A[i]和A[j]互换;
    5)重复第3、4步,直到i=j; (3,4步中,没找到符合条件的值,即3中A[j]不小于key,4中A[i]不大于key的时候改变j、i的值,使得j=j-1,i=i+1,直至找到为止。找到符合条件的值,进行交换的时候i, j指针位置不变。另外,i==j这一过程一定正好是i+或j-完成的时候,此时令循环结束)。

    示例

    假设用户输入了如下数组:
    下标
    0
    1
    2
    3
    4
    5
    数据
    6
    2
    7
    3
    8
    9
    创建变量i=0(指向第一个数据), j=5(指向最后一个数据), k=6(赋值为第一个数据的值)。
    我们要把所有比k小的数移动到k的左面,所以我们可以开始寻找比6小的数,从j开始,从右往左找,不断递减变量j的值,我们找到第一个下标3的数据比6小,于是把数据3移到下标0的位置,把下标0的数据6移到下标3,完成第一次比较:
    下标
    0
    1
    2
    3
    4
    5
    数据
    3
    2
    7
    6
    8
    9
    i=0 j=3 k=6
    接着,开始第二次比较,这次要变成找比k大的了,而且要从前往后找了。递加变量i,发现下标2的数据是第一个比k大的,于是用下标2的数据7和j指向的下标3的数据的6做交换,数据状态变成下表:
    下标
    0
    1
    2
    3
    4
    5
    数据
    3
    2
    6
    7
    8
    9
    i=2 j=3 k=6
    称上面两次比较为一个循环。
    接着,再递减变量j,不断重复进行上面的循环比较。
    在本例中,我们进行一次循环,就发现i和j“碰头”了:他们都指向了下标2。于是,第一遍比较结束。得到结果如下,凡是k(=6)左边的数都比它小,凡是k右边的数都比它大:
    下标
    0
    1
    2
    3
    4
    5
    数据
    3
    2
    6
    7
    8
    9
    如果i和j没有碰头的话,就递加i找大的,还没有,就再递减j找小的,如此反复,不断循环。注意判断和寻找是同时进行的。
    然后,对k两边的数据,再分组分别进行上述的过程,直到不能再分组为止。
    注意:第一遍快速排序不会直接得到最终结果,只会把比k大和比k小的数分到k的两边。为了得到最后结果,需要再次对下标2两边的数组分别执行此步骤,然后再分解数组,直到数组不能再分解为止(只有一个数据),才能得到正确结果。
     
     
     1 Array.prototype.partition = function (start, end) {
     2     var i = start; //首元素
     3     var j = end; //最后一个元素
     4     var key = this[i]; //设置标兵
     5     var temp;
     6     while (j > i) {  //随着j--和i++,必然会相遇,这时候当前小标两边都排好序了
     7         
     8         //注意,如果右边换过一次,跳出循环,再从左边开始找。
     9         while (j > i) {
    10             if (this[j] < key) {
    11                 temp = this[j];
    12                 this[j] = this[i];
    13                 this[i] = temp;
    14                 break;
    15             } else {
    16                 --j;
    17             }
    18         }
    19         
    20         while (j > i) {
    21             if (this[i] > key) {
    22                 temp = this[i];
    23                 this[i] = this[j];
    24                 this[j] = temp;
    25 
    26                 break;
    27             }
    28             ++i;
    29         }
    30 
    31     }
    32     console.log(i);
    33     console.log(A);
    34 
    35     return i;
    36 };
    37 
    38 Array.prototype.QUICK_SORT = function (start, end) {
    39     if (end > start) {
    40         var q = this.partition(start, end);
    41         arguments.callee.call(this,start, q - 1);
    42         arguments.callee.call(this,q + 1, end);
    43     }
    44 };
    45 
    46 var A = [3, 3, 4, 2,6,3,7,21,734,3265,2,4,60,0];
    47 console.log(A);
    48 A.QUICK_SORT(0, A.length-1);

    下面给出算法导论里的伪代码,它的伪代码其实更加优秀:修改的只是partition部分。

    先给出伪代码和过程:

     

     

     

    下面给出实现代码:

     1 Array.prototype.partition = function (p, r) {
     2     var x = this[r];
     3     var i = p - 1;
     4 
     5     for (var j = p; j < r; j++) {
     6         if (this[j] <= x) {
     7             i++;
     8             this.swap(i, j);
     9         }
    10     }
    11     this.swap(i + 1, r);
    12     return i + 1;
    13 };
    14 
    15 Array.prototype.swap = function (i, j) {
    16     var temp = this[i];
    17     this[i] = this[j];
    18     this[j] = temp;
    19 };
    20 
    21 Array.prototype.QUICK_SORT = function (p, r) {
    22     if (r > p) {
    23         var q = this.partition(p, r);
    24         arguments.callee.call(this, p, q - 1);
    25         arguments.callee.call(this, q + 1, r);
    26     }
    27 };
    28 
    29 var A = [3, 3, 4, 2, 6, 3, 7, 21, 734, 3265, 2, 4, 60, 0];
    30 console.log(A);
    31 A.QUICK_SORT(0, A.length - 1);
    32 console.log(A);

    这个代码的优势很明显:首先不需要考虑越界了,然后就是循环少了。

     下面来探讨它的性能问题:

     我们用代换法来证明T(n) = T(n-1)+ T(0) + theta(n) = T(n-1) + theta(n);  T(n) = T(n - 1) + theta(n) = theta(n) + theta(n-1) + ... + theta(1) = theta(n^2)

    思考:

    当数组A的所有元素都具有相同值时,QUICKSORT的时间复杂度是什么?  

     分析:

    当数组A所有元素相同时,QUICKSORT中的划分时极为不平衡的,n-1:0的划分,T(n)=T(n-1)+Θ(n)解这个递归式T(n)=Θ(n^2) 

     思考:

    银行经常会按照交易时间,来记录某一账户的交易情况。但是,很多人却喜欢收到银行对账单是按照支票号码的顺序来排列的。这是因为,人们通常  都是按照支票号码的顺序来开出支票的,而商人也通常都是根据支票编号的顺序兑付支票。这一问题时按照交易时间排序的序列转换成按支票号排序的序列,它是指上是一个对几乎有序的输入序列进行排序的问题。请证明:在这个问题上,INSERTION-SORT的性能往往要优于QUICKSORT?

    分析:

    插入排序在基本有序的情况下,基本无需移动任何元素来插入,所以只有外层循环了n次,所以时间复杂度为O(n)

     快速排序在基本有序的情况下,在划分数组时,划分得非常不平衡,那么其时间复杂度是O(nlgn),而达到完全有序时,时间 复杂度达到O(n^2),所以总之插入排序要优于快速排序。 

    思考:

    假设快速排序的每一层所做的划分的比例都是1-a:a,其中0<a<=1/2且是一个常数。是证明,在相应的递归树中,叶结点的最小深度大约是  -lgn/lga,最大深度大约是-lgn/lg(1-a)(无需考虑整数舍入问题) 

    分析:

    最小深度为每次划分后都是选择最小的一部分继续往下走,每次乘以a。一次迭代减少的元素数从n到an,迭代m次直到剩下的元素为1。

    则(a^m)*n = 1, a^m = 1/n,取对数得mlga = -lgn,m = -lgn/lga。

    同理可得((1-a)^M)*n = 1,M = -lgn/lg(1-a)。

    思考:

    试证明:在一个随机输入数组上,对于任何常数0<a<=1/2,PARTITION产生比1-a:a更平衡的划分的概率约为1-2a      

    证明:

    则X+Y=n  那么根据书上根据平衡的定义,X-Y差值越大,比例就越高,那么越不平衡,只有X-Y差值越小,越接近0,X约等于Y的时候  越平衡。

     分三种情况讨论:1)当X/n<a时,那么Y/n>1-a, |X-Y|/n>1-2a>0               

                                  2)当X/n>1-a时,那么Y<a, |X-Y|/n>1-2a>0         

                                  3)当a<X/n<1-a时,那么a<Y<1-a,0<|X-Y|/n<1-2a

     只有当|X-Y|/n距离小于一个数时,才可能X-Y差值趋向于0,划分的就越平衡。所以我们选择情况3的这种划分。  在an与(1-a)n之间取X的值,因为划分X落在区间[0,n]上是等可能性的,所以符合均匀分布,落在[an,(1-a)n}]上的任意一点  的概率也是等可能的,所以P{an≤x≤(1-a)n}=((1-a)n-an)/(n-0)=1-2a。得证! 

    思考:为什么我们分析随机化算法的期望运行时间,而不是其最坏运行时间呢?

     分析:随机化算法不能改变最坏情况下得运行时间,但是能降低最坏情况发生的概率。

    思考:

    在RANDOMIZED-QUICKSORT的运行过程中,在最坏情况下,随机数生成器RANDOM被调用了多少次?在最好情况下呢? 

     分析:

    最好情况是均匀划分,其时间复杂度 T(n)=2T(n/2)+1 =>主定理case1得T(n)=Θ(n)   

    最坏情况是分成不平衡的划分,其时间复杂度 T(n)=T(n-1)+T(0)+1 各式相加得=>T(n)=Θ(n)  

     

     

     

     

     

     

  • 相关阅读:
    cocos2dx中的精灵CCSprite
    Mark Russinovich 的博客:Windows Azure 主机更新:原因、时间和方式
    cocos2d-x中的尺寸之三
    cocos2d-x中的尺寸之二
    cocos2d-x中的尺寸之一
    cocos2d-x中的导演、场景、层和精灵
    宣布正式发布 Windows Azure 上的 Oracle 软件以及 Windows Azure Traffic Manager 更新
    Hello China操作系统STM32移植指南(三)
    Hello China操作系统STM32移植指南(二)
    diff_mysql_table_exec.py
  • 原文地址:https://www.cnblogs.com/huenchao/p/5908773.html
Copyright © 2011-2022 走看看