快速排序

一、算法性能

  • 快速排序平均性能良好,平均运行时间为O(nlgn);
  • 最坏情况是每次划分选取的基准都是当前无序区中关键字最小(或最大)的记录,划分的结果是基准左边的子区间为空(或右边的子区间为空),而划分所得的另一个非空的子区间中记录数目,仅仅比划分前的无序区中记录个数减少一个。时间复杂度为O(n*n)。当待排序的序列是完全有序的时候,快速排序递归就会达到最高代价。

二、基本原理:选择一个基准记录;从左到右扫描记录表,找到关键字大于等于基准的记录,同时从右到左扫描记录表,找到小于等于基准的记录,交换两个记录。

三、关于基准记录的选择

1、取首;

2、选择当前记录表的第一、中间和最后一个记录中关键字为中值的记录。提高选择均等划分记录表的可能性。k=3

四、代码

 1 #include<iostream>
 2 
 3 using namespace std;
 4 
 5 int Partition(int* A, int p, int r)
 6 {
 7     int x = A[r];
 8     int i = p-1;
 9     int j,temp;
10     for (j=p;j<r;j++)
11     {
12         if (A[j]<=x)
13         {
14             i++;
15             temp = A[j];
16             A[j] = A[i];
17             A[i] = temp;
18         }
19     }
20     temp = A[i+1];  //exchange
21     A[i+1] = A[r];
22     A[r] = temp;
23     return (i+1);
24 }
25 
26 
27 void QuickSort(int *A, int p, int r)
28 {
29     int q;
30     if (p<r)
31     {
32         q = Partition(A, p, r);
33         QuickSort(A, p, q-1);
34         QuickSort(A, q+1, r);
35     }
36 }
37 
38 void ImpQuickSort(int* A, int p, int r)
39 {
40     int q;
41     while (p<r)
42     {
43         q = Partition(A, p, r);
44         if ((q-p)<(r-q))
45         {
46             ImpQuickSort(A, p, q-1);
47             p = q+1;
48         }
49         else
50         {
51             ImpQuickSort(A, q+1, r);
52             r = q-1;
53         }
54     }
55 }
56 
57 int RandomizedSelect(int* A, int p, int r, int i)
58 {
59     int q, k;
60     if (p==r)
61         return A[p];
62     q = Partition(A, p, r);
63     k = q-p+1;
64     if (k==i)
65         return A[q];
66     else if (i<k)
67         RandomizedSelect(A,p,q-1,i);
68     else
69         RandomizedSelect(A,q+1,r,i-k);
70 }
71 
72 int main()
73 {
74     int n;
75     int *a;
76     cin>>n;
77     a = new int[n];
78     for (int i=0;i<n;i++)
79     {
80         cin>>a[i];
81     }
82     QuickSort(a,0,n-1);
83     for (int i=0;i<n;i++)
84         cout<<a[i]<<' ';
85     cout<<endl;
86     cout<<RandomizedSelect(a,0,n-1,5);
87     return 0;
88 }
View Code

 

五、改进方法

1、改进基准选择方案

  • 随机选择基准
  • 选择当前记录表的第一、中间和最后一个记录中关键字为中值的记录。提高选择均等划分记录表的可能性。
  • 每次取数据集的中位数作为基准

    选取中位数平均运行时间是O(n),最坏运行时间O(n*n)。

 1 int RandomizedSelect(int* A, int p, int r, int i)
 2 {
 3     int q, k;
 4     if (p==r)
 5         return A[p];
 6     q = Partition(A, p, r);
 7     k = q-p+1;
 8     if (k==i)
 9         return A[q];
10     else if (i<k)
11         RandomizedSelect(A,p,q-1,i);
12     else
13         RandomizedSelect(A,q+1,r,i-k);
14 }
View Code

 

2、用插入排序改进

  当数据量较小时,快速排序相对较慢,其递归结构必然导致反复排序小序列。

  • 改进方法一:使用排序小序列较快的方法代替快速排序,比如插入排序;具体小到何种规模时采用插入排序,一些文章中说5—25 之间。SGI STL 中的快速排序采用的值是 10。
  • 改进方法二:当需要排序的记录表小于一定长度时,什么也不做。从整个记录表来看,此时整个记录表接近于排好序,正是利用插入排序性能最好的情况,对整个表调用一次插入排序。实验表明,子表长度小于9时,可采用此策略。

3、减小递归深度

  递归执行需要栈空间,如果记录被均匀划分,则算法的最大递归深度是lgn,需要栈空间O(lgn);如果是最坏情况,记录表被划分成长度为n-1和1的子表,则算法的递归深度是n,需要栈空间O(n)。为了减小递归深度,可以递归排序两个子表中较小的,在用循环代替第二个递归,使得算法递归深度最多为O(lgn)。

  利用了尾递归:

  摘自:http://www.cnblogs.com/Anker/archive/2013/03/04/2943498.html

  顾名思义,尾递归就是从最后开始计算, 每递归一次就算出相应的结果, 也就是说, 函数调用出现在调用者函数的尾部, 因为是尾部, 所以根本没有必要去保存任何局部变量. 直接让被调用的函数返回时越过调用者, 返回到调用者的调用者去。尾递归就是把当前的运算结果(或路径)放在参数里传给下层函数,深层函数所面对的不是越来越简单的问题,而是越来越复杂的问题,因为参数里带有前面若干步的运算路径。

  尾递归是极其重要的,不用尾递归,函数的堆栈耗用难以估量,需要保存很多中间函数的堆栈。比如f(n, sum) = f(n-1) + value(n) + sum; 会保存n个函数调用堆栈,而使用尾递归f(n, sum) = f(n-1, sum+value(n)); 这样则只保留后一个函数堆栈即可,之前的可优化删去。

  

 1 void ImpQuickSort(int* A, int p, int r)
 2 {
 3     int q;
 4     while (p<r)
 5     {
 6         q = Partition(A, p, r);
 7         if ((q-p)<(r-q))
 8         {
 9             ImpQuickSort(A, p, q-1);
10             p = q+1;
11         }
12         else
13         {
14             ImpQuickSort(A, q+1, r);
15             r = q-1;
16         }
17     }
18 }
View Code

 

 

  以上三点综合利用大概可以提高原本快速排序20%-30%的性能。

4、三分区

  对于每个元素完全相同的序列来讲,快速排序也会退化到 O(n^2)可以将快速排序的二分区变成三分区:在分区的时候,将序列分为 3 堆,一堆小于基准元素,一堆等于基准元素,一堆大于基准元

素,下次递归调用快速排序的时候,只需对小于和大于基准元素的两堆数据进行排序,中间等于基准元素的一堆已经放好。

 

posted on 2017-03-10 19:56  Valenpqq  阅读(152)  评论(0)    收藏  举报