演算法導論-快速排序

QingLiXueShi發表於2015-02-16

一、快速排序的描述

快速排序是基於分治策略的。對一個子陣列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]中的元素。下標q也在這個劃分過程中進行計算。

2、解決

通過遞迴呼叫快速排序,對子陣列A[p…q-1]和A[q+1…r]排序。

3、合併

因為兩個子陣列就是原地排序的,將它們的合併不需要操作:整個陣列A[p…r]已排序。

 

快速排序的參考程式碼如下:

 1 class Solution
 2 {
 3 public:
 4     void QuickSort(int *a, int length);
 5     void QSort(int *a, int low, int high);
 6     int Partition(int *a, int low, int high);
 7 };
 8 
 9 void Solution::QuickSort(int *a, int length)
10 {
11     QSort(a, 0, length - 1);
12 }
13 
14 void Solution::QSort(int *a, int low, int high)
15 {
16     if (low < high)
17     {
18         int p = Partition(a, low, high);
19         QSort(a, low, p - 1);
20         QSort(a, p + 1, high);
21     }
22 }
23 
24 int Solution::Partition(int *a, int low, int high)
25 {
26     int key = a[high], tmp;
27     int i = low - 1, j;
28 
29     for (j = low; j < high; ++j)
30     {
31         if (a[j] <= key)
32         {
33             i = i + 1;
34 
35             tmp = a[j];            //交換a[j]和a[i]
36             a[j] = a[i];
37             a[i] = tmp;
38         }
39     }
40 
41     tmp = a[high];                //交換a[high]和a[i + 1]
42     a[high] = a[i + 1];
43     a[i + 1] = tmp;
44 
45     return i + 1;
46 }

函式Partition()的另外一種直觀寫法如下:

 1 int Solution::Partition(int *a, int low, int high)
 2 {
 3     int key = a[low];
 4     
 5     while (low < high)
 6     {
 7         while (low < high && key <= a[high])
 8         {
 9             --high;
10         }
11         a[low] = a[high];
12 
13         while (low < high && key >= a[low])
14         {
15             ++low;
16         }
17         a[high] = a[low];
18     }
19     a[low] = key;
20 
21     return low;
22 }

二、快速排序的效能

快速排序的執行時間與劃分是否對稱有關,而後者又與選擇了哪一個元素來進行劃分有關。如果劃分是對稱的,演算法從漸進意義上就與歸併排序一樣快;如果劃分是不對稱的,演算法從漸進意義上就和插入演算法一樣慢。函式Partition()在子陣列A[p…r]上的執行時間為,其中n=r-p+1。

1、最壞情況劃分

最壞情況劃分發生在劃分過程中產生的兩個區域分別包含n-1個元素和1個0元素的時候。假設演算法的每一次遞迴呼叫都出現這種不對稱劃分。劃分的時間代價為,對一個大小為0的陣列進行遞迴呼叫的時間代價為演算法的執行時間可表示為:

,解出

因此,如果在演算法的每一層遞迴上,劃分都是最大程度不對稱的,演算法的執行時間就是。從而,快速排序演算法的最壞情況執行時間並不比插入排序的更好。例如:當輸入陣列已經完全排好序時,快速排序的執行時間為,插入排序的執行時間為

 

2、最佳情況劃分

函式Partition()可能做的最平衡的劃分中,得到的兩個子問題的大小接近n/2。在這種情況下,快速排序執行的速度要快得多。演算法的執行時間可表示為:,解出

 

3、平衡的劃分

快速排序的平均情況執行時間與其最佳情況執行時間很接近,而不是非常接近於其最壞情況執行時間。例如:假設劃分過程總是產生9:1的劃分,演算法的執行時間可表示為:這一遞迴式對應的遞迴樹如圖:

該樹每層代價都是cn,直到在深度處終止。這樣快速排序的總代價為

在遞迴的每一層上按照9:1的比例進行劃分,直觀上看好像相當不平衡,但從漸進意義上,這與在正中間劃分的效果是一樣的。事實上按照99:1劃分執行時間也為,原因在於任何一種按照常數比例進行的劃分都會產生深度為的遞迴樹,其中每一層的代價為,因而,每當按照常數比例劃分時,總的執行時間都是

 

三、結論

1、優點:因為每趟可以確定不止一個元素的位置,而且呈指數增加,所以特別快。前提:順序儲存結構。

2、改變劃分元素的選取方法,至多隻能改變演算法平均情況下的時間效能,無法改變最壞情況下的時間效能。

3、是一種不穩定的排序演算法,是原地排序。

4、對包含n個數的輸入陣列,最壞執行時間為。雖然這個最壞情況執行時間比較差,但快速排序通常是用於排序的最佳實用選擇,因為其平均效能為,且記號中隱含的常數因子很小。在虛擬環境中也能很好地工作。

相關文章