快速排序 Quick Sort
快速排序的基本思想是,通過一趟排序將待排記錄分割成獨立的兩部分,其中一部分記錄的關鍵字均比另一部分記錄的關鍵字小,則可分別對這兩部分記錄繼續進行排序,以達到整個序列有序。
一趟快速排序(或一次划分)的過程如下:首先任意選取一個記錄(通常可選第一個記錄)作為樞軸(或支點)(pivot),然后按下列原則重新排列其余記錄:將所有關鍵字比它小的記錄都安置在它的位置之前,將所有關鍵字比它大的記錄都安置在它的位置之后。
經過一趟快速排序之后,以該樞軸記錄最后所落的位置i作分界線,將序列分割成兩個子序列,之后再分別對分割所得的兩個子序列進行快速排序。
可以看出這個算法可以遞歸實現,可以用一個函數來實現划分,並返回分界位置。然后不斷地這么分下去直到排序完成,可以看出函數的輸入參數需要提供序列的首尾位置。
快速排序的實現
划分實現1 (樞軸跳來跳去法)
一趟快速排序的實現:設兩個指針low和high,設樞軸記錄的關鍵字為pivotkey,則首先從high所指位置起向前搜索找到第一個關鍵字小於pivotkey的記錄和樞軸記錄互相交換,然后從low所指位置起向后搜索,找到第一個關鍵字大於pivotkey的記錄和樞軸記錄互相交換,重復這兩步直至low==high為止。
下面的代碼例子元素類型為int,並且關鍵字就是其本身。

typedef int ElemType; int Patition(ElemType A[], int low, int high) { ElemType pivotkey=A[low]; ElemType temp; while(low<high) { while(low <high && A[high]>=pivotkey) { --high; } temp=A[high]; A[high]=A[low]; A[low]=temp; while(low<high && A[low]<=pivotkey) { ++low; } temp=A[high]; A[high]=A[low]; A[low]=temp; } return low; }
划分實現2 (樞軸一次到位法)
從上面的實現可以看出,樞軸元素(即最開始選的“中間”元素(其實往往是拿第一個元素作為“中間”元素))在上面的實現方法中需要不斷地和其他元素交換位置,而每交換一次位置實際上需要三次賦值操作。
實際上,只有最后low=high的位置才是樞軸元素的最終位置,所以可以先將樞軸元素保存起來,排序過程中只作元素的單向移動,直至一趟排序結束后再將樞軸元素移至正確的位置上。
代碼如下:

int Patition(ElemType A[], int low, int high) { ElemType pivotkey=A[low]; ElemType temp = A[low]; while(low<high) { while(low <high && A[high]>=pivotkey) { --high; } A[low]=A[high]; while(low<high && A[low]<=pivotkey) { ++low; } A[high]=A[low]; } A[low] = temp; return low; }
可以看到減少了每次交換元素都要進行的三個賦值操作,變成了一個賦值操作。
細節就是每次覆蓋掉的元素都已經在上次保存過了,所以不必擔心,而第一次覆蓋掉的元素就是樞軸元素,最后覆蓋在了它應該處於的位置。
遞歸形式的快速排序算法

void QuickSort(ElemType A[], int low, int high) { if(low<high) { int pivotloc=Patition(A,low, high); QuickSort(A, low, pivotloc-1); QuickSort(A, pivotloc+1, high); } }
不管划分是上面哪一種實現,都可以用這個遞歸形式進行快速排序。
需要注意的是這個if語句不能少,不然沒法停止,會導致堆棧溢出的異常。
快速排序的性能分析
時間復雜度
快速排序的平均時間為Tavg(n)=knln(n),其中n為待排序列中記錄的個數,k為某個常數,在所有同數量級的先進的排序算法中,快速排序的常數因子k最小。
因此,就平均性能而言,快速排序是目前被認為是最好的一種內部排序方法。通常認為快速排序在平均情況下的時間復雜度為O(nlogn)。
但是,快速排序也不是完美的。
若初始記錄序列按關鍵字有序或基本有序,快速排序將蛻化為冒泡排序,其時間復雜度為O(n2)。
原因:因為每次的樞軸都選擇第一個元素,在有序的情況下,性能就蛻化了。
如下圖:
快速排序的空間利用情況
從空間上看,快速排序需要一個棧空間來實現遞歸。
若每一趟排序都將記錄序列分割成長度相接近的兩個子序列,則棧的最大深度為log2n+1(包括最外層參量進棧);但是,若每趟排序之后,樞軸位置均偏向子序列的一端,則為最壞情況,棧的最大深度為n。
如果在一趟划分之后比較分割所得兩部分的長度,且先對長度短的子序列中的記錄進行快速排序,則棧的最大深度可降為O(logn)。
性能改善
為改進快速排序算法,隨機選取界點或最左、最右、中間三個元素中的值處於中間的作為界點,通常可以避免原始序列有序的最壞情況。
然而,即使如此,也不能使快速排序在待排記錄序列已按關鍵字有序的情況下達到O(n)的時間復雜度(冒泡排序可以達到)。
為此,可以如下修改划分算法:在指針high減去1和low增加1的同時進行“起泡”操作,即在相鄰兩個記錄處於“逆序”時進行互換,同時在算法中附設兩個布爾型變量分別指示指針low和high在從兩端向中間移動的過程中是否進行過交換記錄的操作,若沒有,則不需要對低端或高端子表進行排序,這將進一步改善快速排序的平均性能。
另外,將遞歸算法改為非遞歸算法,也將加快速度,因為避免了進出棧和恢復斷點等工作。