• 快速排序分析


    快速排序是比较经典的算法,所以我把在编程珠玑上的讲解整理了一下,也当给自己理清思路。在某些特定情况下,在空间和时间上应该还有很多优化的策略,所以以后会继续学习优化的方法。
    一、单向划分的快排实现
    该程序不会导致无限递归调用,因为每次都排除了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)对数组排序,其实现也非常高效,若系统的排序能满足我们的要求,就不用考虑自己编写代码了。

  • 相关阅读:
    类型转换
    struts2默认拦截器
    struts2自定义拦截器
    struts2之I18N
    代理模式
    抽象类 abstract class 接口
    java基础题
    final
    内部类
    tomcat 端口占用问题解决
  • 原文地址:https://www.cnblogs.com/yimindu/p/3366646.html
Copyright © 2020-2023  润新知