快速排序分析

快速排序是比较经典的算法,所以我把在编程珠玑上的讲解整理了一下,也当给自己理清思路。在某些特定情况下,在空间和时间上应该还有很多优化的策略,所以以后会继续学习优化的方法。
一、单向划分的快排实现
该程序不会导致无限递归调用,因为每次都排除了x[m],与二分搜索会终止的原理一样。
当输入数组是不同元素的随机排列时,该快速排序的平均时间复杂度是O(n log n),栈空间为O(log n).任何基于比较的排序至少需要O( n log n)次比较,因此快速排序接近最优算法。
c库函数的快速排序算法的通用接口开销很大,所以本程序可能适合与一些表现良好的应用程序,但在一些常见的输出下,本程序可能会退化为平方时间算法。

 1 #include<iostream>
 2 using namespace std;
 3 int x[100];
 4 void qsort1(int l , int u)
 5 {
 6     if(l >= u)
 7         return ;
 8     int m = l;
 9     for(int i = l + 1; i <= u; i++)
10     {
11         if(x[i] < x[l])       
12             swap(x[++m] , x[i]);
13     }
14     swap(x[l] , x[m]);
15     qsort1(l , m - 1);
16     qsort1(m + 1 , u);
17 }
18 int main()
19 {
20     int n;
21     cin>>n;
22     for(int i = 0; i < n;i++)
23     {
24          cin>>x[i];
25     }
26     qsort1(0 , n - 1);
27     for(int i = 0 ; i < n; i++) 
28     {
29         cout<<x[i]<<" ";
30     }  
31     cout<<endl;
32     return 0;
33 }        


二、单向划分加哨兵优化
对qsort1的一点优化。将划分方案从右向左运行,由于循环结束时x[m] = t;所以可直接使用参数(l , m-1)和(m+1 , u)进行递归,不再需要swap操作,用x[l]作为哨兵还省去了内循环中的一次比较。

 1 #include<iostream>
 2 using namespace std;
 3 int x[100];
 4 void qsort2(int l , int u)
 5 {
 6     int i , m , t;
 7     if(l >= u)
 8         return ;
 9     i = m = u + 1;
10     t = x[l];
11     do{
12         while(x[--i] < t)
13             ;
14         swap(x[--m] , x[i]);
15     }while(i != l);
16     qsort2(l , m - 1);
17     qsort2(m + 1 , u);
18 }
19 int main()
20 {
21     int n;
22     cin>>n;
23     for(int i = 0; i < n; i++)
24         cin>>x[i];
25     qsort2(0 , n - 1);
26     for(int i = 0 ; i < n; i++)
27         cout<<x[i]<<" ";
28     cout<<endl;
29     return 0;
30 }


三、双向划分的快速排序
n个相同元素组成的数组,对于这种输入,插入排序的性能比较好,每个与素需要移动的距离都是0,所以总的运行时间是O(n),但是qsort1算法的性能比较糟糕,n-1次的划分中每次划分都需要O(n)时间来去掉一个元素,所以总的运行时间是O(n^2)。使用双向划分可以避免这问题。采用双向划分,当输入元素相时,如果采用向右跳过避免多与操作的方式,还是会得到平方时间的算法(注意向右的do循环会执行到底),正确的做法是当遇到相同元素是停止扫描,交换i和j 的元素值。这样做交换的次数增加了,但是将所有元素都同的最坏情况变成了差不多需要nlog2n次比较的最好情况。

 1 #include<iostream>
 2 using namespace std;
 3 int x[100];
 4 void qsort3(int l , int u)
 5 {
 6     if(l >= u)
 7         return ;
 8     int t = x[l];
 9     int i = l;
10     int j = u + 1;
11     while(1){
12         do{
13           i++;
14         }while(i <= u && x[i] < t);
15         do{
16             j--;
17         }while(x[j] > t);
18         if(i > j)
19             break;
20         swap(x[i] , x[j]);
21     }
22     swap(x[l] , x[j]);
23     qsort3(l , j - 1);
24     qsort3(j + 1 , u);   
25 }
26 int main()
27 {
28     int n;
29     cin>>n;
30     for(int i = 0; i < n; i++)
31         cin>>x[i];
32     qsort3(0 , n - 1);
33     for(int i = 0 ; i < n; i++)
34         cout<<x[i]<<" ";
35     cout<<endl;
36     return 0;
37 }


四、采用随机化优化的快速排序
对于随机输入,以上算法没问题,但是对于某些输入,这种算法的时间和空间都偏多。例如,如果数组已经按升序排列好了,或者已经按降序排列好了,就会围绕最小或者最大,然后次小或次大划分下去,总共需要O(n^2)的时间。随机选择划分元素就可以得到好得多的性能,我们通过把x[l]和x[l...u]中的一个随机项想交换来实现这一点swap(l,randint(l , u));结合了随机划分元素和双向划分以后,对于任意输入,快排的期望运行时间都正比于nlog n,随机情况下的性能边界是通过调用随机数生成器得到的,而不是通过对输入的分布进行假设得到的。
用插入排序来排序很小的子数组,可以继续提高性能。
cutoff用以停止继续划分,一般选取cutoff为50。

 1 #include<iostream>
 2 using namespace std;
 3 int x[100];
 4 void isort3(int l , int u)
 5 {
 6     int j;
 7     for(int i = l ; i <= u ;i++){
 8         int t = x[i];
 9         for(j = i ; j > 0 && x[j-1] > t ;j--)
10             x[j] = x[j-1];
11         x[j] = t;
12     }
13 }
14 int randint(int l , int u)
15 {
16     return l + rand()%(u - l);
17 }
18 void qsort4(int l , int u)
19 {
20     if(u - l < 50){
21         isort3(l , u);
22         return;
23     }
24     swap(x[l] , x[randint(l , u)]);
25     int t = x[l];
26     int i = l;
27     int j = u + 1;
28     while(1){
29       do{
30         i++;
31       }while(i <= u && x[i] < t);
32       do{
33         j--;
34       }while(x[j] > t);
35       if(i > j)
36         break;
37       swap(x[i] , x[j]);
38     }
39     swap(x[l] , x[j]);
40     qsort4(l , j - 1);
41     qsort4(j + 1 , u);   
42 }
43 int main()
44 {
45     int n;
46     cin>>n;
47     for(int i = 0; i < n; i++)
48         cin>>x[i];
49     qsort4(0 , n - 1);
50     for(int i = 0 ; i < n; i++)
51         cout<<x[i]<<" ";
52     cout<<endl;
53     return 0;
54 }


五、快速排序总结
C库函数qsort非常简单并且相对比较快,比我们自己写的快排慢,仅仅因为其通用灵活的接口对每次比较都是用函数调用。c++库函数sort具有最简单的接口:我们通过调用sort(x,x+n)对数组排序,其实现也非常高效,若系统的排序能满足我们的要求,就不用考虑自己编写代码了。

posted @ 2013-10-13 14:38  YiminDu  阅读(343)  评论(0编辑  收藏  举报