zoukankan      html  css  js  c++  java
  • 快速排序算法

    一、快速排序算法的基本特性
    时间复杂度:O(n*lgn)
    最坏:O(n^2)
    空间复杂度:O(n*lgn)
    不稳定。

    快速排序是一种排序算法,对包含n个数的输入数组,平均时间为O(nlgn),最坏情况(已经排好序)是O(n^2),最好情况(完全无序)是O(nlgn)。
    通常是用于排序的最佳选择。因为,基于比较的排序,最快也只能达到O(nlgn)。


    二、快速排序算法的描述
    算法导论,第7章
    快速排序时基于分治模式处理的,
    对一个典型子数组A[p...r]排序的分治过程为三个步骤:
    1.分解:
    A[p..r]被划分为俩个(可能空)的子数组A[p ..q-1]和A[q+1 ..r],使得
    A[p ..q-1] <= A[q] <= A[q+1 ..r]
    2.解决:通过递归调用快速排序,对子数组A[p ..q-1]和A[q+1 ..r]排序。
    3.合并。

    三、快速排序算法

    快速排序算法的版本很多,这其实也是基于每个人自己的思维方式不同,接受和理解方式不同来区分。并没有明显的优劣之分,只要自己能理解,就可以啦!接下来我要说的就是算法导论上的那个版本。

    QUICKSORT(A, p, r)
    1 if p < r
    2    then q ← PARTITION(A, p, r)   //关键
    3         QUICKSORT(A, p, q - 1)
    4         QUICKSORT(A, q + 1, r)


    快速排序算法的关键是PARTITION过程(数组划分),它对A[p..r]进行就地重排:
    PARTITION(A, p, r)
    1  x ← A[r]    //以最后一个元素,A[r]为主元
    2  i ← p - 1
    3  for j ← p to r - 1    //j从p指向的是r-1,不是r。
    4       do if A[j] ≤ x
    5             then i ← i + 1
    6                  exchange A[i] <-> A[j]
    7  exchange A[i + 1] <-> A[r]
    8  return i + 1

    举个例子说明一下这个数组划分的过程:2   8   7   1   3   5   6   4

    注意:以最后一个元素4为主元,且i,j俩指针都从头出发,j 一前,i 一后。i 指元素的前一个位置,j 指着待排序数列中的第一个元素。

    i p/j                              r

      2   8   7   1   3   5   6   4(主元)
    j指的元素是2,因为2<=4(主元),于是i++,i也指到元素2,所以A[i] <-> A[j],也就是2和2互换,原数组不变。
    然后,j继续后移,直到指向1。

      p/i           j                  r

      2   8   7   1   3   5   6   4(主元) 

    j指向元素1,因为1<=4(主元),于是i++,i指向了8,所以8与1交换。

    数组变成了:
      p   i         j                  r
      2   1   7   8   3   5   6   4

      p   i              j             r
      2   1   7   8   3   5   6   4

    然后j继续后移,指向了元素3,因为3<=4(主元),于是i++,i这是指向了7,于是7与3交换。
    数组变成了:
      p        i         j             r
      2   1   3   8   7   5   6   4

    j继续后移,一直到r-1的位置,都发现没有再比4小的数,所以,执行到了PARTITION过程的最后一步,

    即上述PARTITION(A, p, r)代码部分的 第7行。
    因此,i后移一个单位,指向了8
      p             i              j    r
      2   1   3   8   7   5   6   4
    执行A[i + 1] <-> A[r],即8与4交换,所以,数组最终变成了如下形式,
      2   1   3   4   7   5   6   8
    至此,快速排序第一趟完成。


    4(主元)把整个数组分成了俩部分,2 1 3和7 5 6 8,再递归对这俩部分分别快速排序。
    i p/j
      2   1   3(主元)
    2与2互换,不变,然后又是1与1互换,还是不变,最后,3与3互换,不变,
    最终,3(主元)把2 1 3,分成了俩部分,2 1和3。
    再对2 1,递归排序,最终结果成为了1 2 3。

    7 5 6 8(主元),7、5、6、都比8小,所以第一趟,还是7 5 6 8,
    不过,此刻8把7 5 6 8,分成了  7 5 6和8。[7 5 6->5 7 6->5 6 7]
    再对7 5 6,递归排序,最终结果变成5 6 7 8。

    ok,所有快速排序的过程,全部分析完成。

    最后还得再说说快速排序中最关键的PARTITION过程(数组划分),由上述过程,可看出每一次PARTITION过程中,j扫描了整个数组一遍,只要一旦遇到比4(主元)小的元素,i 就++,然后,kj、ki交换。那么,为什么当j找到比4小的元素后,i 要++了? 你想了,如果i始终停在原地不动,与kj 每次交换的ki不就是同一个元素了么?如此,还谈什么排序?。所以,j在前面开路,i跟在j后,j只要遇到比4小的元素,i 就向前前进一步,然后把j找到的比4小的元素,赋给i,然后,j才再前进。

         打个比喻就是,你可以这么认为,i所经过的每一步,都必须是比4小的元素,否则,i就不能继续前行。好比j 是先行者,为i 开路搭桥,把小的元素作为跳板放到i 跟前,为其铺路前行啊。

        于此,j扫描到最后,也已经完全排查出了比4小的元素,只有最后一个主元4,则交给i处理,因为最后一步,exchange A[i + 1] <-> A[r]。这样,不但完全确保了只要是比4小的元素,都被交换到了数组的前面,且j之前未处理的比较大的元素则被交换到了后面,而且还是O(N)的时间复杂度,你不得不佩服此算法设计的巧妙。

    四、快速排序算法的时间复杂度

        ok,大概理解了快速排序,那么,也能很快的判断出:快速排序算法的平均时间复杂度,即为O(nlgn)。为什么了?因为你看,j,i扫描一遍数组,花费用时多少?对了,扫描一遍,当然是O(n)了,那样,扫描多少遍列,lgn到n遍,最快lgn,最慢n遍。且可证得,快速排序的平均时间复杂度即为O(n*lgn)。

        PARTITION可能做的最平衡的划分中,得到的每个子问题都不能大于n/2。因为其中一个子问题的大小为|_n/2_|。另一个子问题的大小为|-n/2-|-1。

        在这种情况下,快速排序的速度要快得多。为:

          T(n)<=2T(n/2)+O(n).可以证得,T(n)=O(nlgn)。

    五、Java代码实现快速排序算法

    public class QuickSort {
        /**
         * 快速排序的主方法
         * @param array :待排序数组
         * @param begin :数组第一个元素的下标(其实就是0)
         * @param end :数组最后一个元素的下标(其实就是数组长度-1)
         * @return
         */
        public static int[] quickSort(int[] array,int begin,int end ) {
            
            if(begin < end){
                int k = partition(array, begin, end);
                quickSort(array,begin,k-1);
                quickSort(array,k+1,end);
            }
            return array;
        }
        public static int partition(int[] data,int head,int hi){
            
             int key=data[hi];  //以最后一个元素,data[hi]为主元
             int i=head-1;
             for(int j=head;j<hi;j++)   ///注,j从p指向的是r-1,不是r。
             {
              if(data[j]<=key){
               i=i+1;
               swap(data,i,j);
              }
             }
             swap(data,i+1,hi);   
             return i+1;
            
        }
        public static void swap(int[] data ,int i ,int j){
            int temp = data[i];
            data[i] = data[j];
            data[j] = temp;
        }
        
        public static void main(String[] args) {
            int[] array = {2,8,7,1,3,5,6,4,9};
            int len = array.length-1;
            array = quickSort(array, 0, len);
            for (int i = 0; i < array.length; i++) {
                System.out.print(array[i]);
            }
        }
    }

     

    整理自:http://blog.csdn.net/v_JULY_v/article/details/6211155

                http://blog.csdn.net/v_JULY_v/article/details/6116297

  • 相关阅读:
    socket选项设置
    shell 备忘录
    VIM中cscope和tags数据库的添加
    MFC程序设计中的BeginPaint/EndPaint和GetDC/ReleaseDC的使用
    shell 命令行参数解析
    do{...}while(0)用法总结
    0长度数组的使用
    在线帮助文档
    GCC编译器帮助文档
    几款优秀的Linux基准测试工具
  • 原文地址:https://www.cnblogs.com/Kevin-mao/p/5764148.html
Copyright © 2011-2022 走看看