排序演算法:快速排序
快速排序
快速排序時目前廣泛應用的一種排序演算法。它的實現簡單,適用於各種不同的輸入資料且在一般應用中比其他排序演算法都要快得多。
基本演算法
快速排序是一種分治的排序演算法。它將一個數組分成兩個子陣列,將兩部分獨立地排序。快速排序和遞迴排序是互補的:歸併排序將兩個陣列分別排序,並將有序的子陣列歸併以將整個陣列排序;而快速排序將陣列排序的方式則是當兩個子陣列都有序時整個陣列也就自然有序了。所以區別就是,在第一種情況中,遞迴呼叫發生在處理整個陣列之前;在第二種情況中,遞迴呼叫發生在處理整個陣列後。在歸併排序中,一個數組被等分成兩半;在快速排序中,切分(partition)
void QuickSort(int arr[], int lo, int hi) {//快速排序
if (lo < hi) {
int pivot = partition(arr, lo, hi);//劃分
QuickSort(arr, lo, pivot - 1);//將左半邊排序
QuickSort(arr, pivot + 1, hi);//將右半邊排序
}
}
該方法的關鍵是切分,這個過程使陣列滿足三個條件:
1.對於某個pivot,arr[pivot] 已經排定;
2.從arr[lo] 到arr[pivot-1] 中所有元素都不大於arr[pivot]
3.從arr[pivot+1] 到arr[hi] 中所有元素都不小於arr[pivot];
我們通過遞迴地呼叫切分來排序。每次切分都會將一個元素排定(這個元素的位置已經固定,不會改變),那麼如果左子陣列和右子陣列都是有序的,那麼由左子陣列、切分元素、右子陣列組成的結果陣列也一定是有序的。這就是快速排序演算法的思路。
對於切分方法,我們一般將切分元素定為arr[lo],即那個將要被排定的元素,先將這個切分元素的數值用臨時變數儲存下來,然後我們讓右指標向左走,找到一個小於切分元素的元素,把值賦給現在左指標指向的元素,然後讓左指標向右走,找到一個大於切分元素的元素,把值賦給現在右指標指向的元素,以此類推。這樣左右交替著進行,直到左右指標相遇,這個位置就是切分元素所應該在的位置,將之前存好的切分元素取出來複製到這裡即可完成一次切分。
int partition(int arr[], int lo,int hi) {//劃分
int pivot = arr[lo];
while (lo < hi) {
while (lo < hi && arr[hi] >= pivot)--hi;
arr[lo] = arr[hi];
while (lo < hi && arr[lo] <= pivot)++lo;
arr[hi] = arr[lo];
}
arr[lo] = pivot;
return lo;
}
演算法改進
實際應用中,經常會出現大量重複元素的陣列,並要求排序。雖然可以用傳統的快速排序解決,但是有巨大的提升空間。例如,一個元素全部重複的子陣列就不需要排序了,可是我們的演算法仍然會將它切分成更小的陣列。
所以“三項切分的快速排序”應運而生。也就是說將陣列切分成三段,分別對應小於、等於、大於切分元素的陣列元素。演算法從左至右遍歷陣列一次,維護一個指標lt使得arr[lo…lt-1] 中的元素都小於pivot,一個指標gt使得arr[gt+1…hi] 中的元素都大於pivot,一個指標i使得arr[lt…i-1] 中的元素都等於pivot,arr[i…gt] 中的元素還未確定。
如上圖所示,我們處理三種情況:
1.arr[i]<pivot,交換arr[lt],arr[i],將lt和i加一;
2.arr[i]>pivot,交換arr[gt],arr[i],將gt減一;
3.arr=pivot,將i加一。
這樣做會縮小gt–i的值,最後得到的就是上圖中切分後的狀態。
void Quick3waySort(int arr[], int lo, int hi) {//三項切分的快速排序
if (hi <= lo)return;
int lt = lo,i = lo + 1,gt = hi;
int pivot = arr[lo];
while (i <= gt) {
int cmp = compareTo(arr[i], pivot);
if (cmp < 0)exchange(arr, lt++, i++);
else if (cmp > 0)exchange(arr, i, gt--);
else i++;
}
Quick3waySort(arr, lo, lt - 1);
Quick3waySort(arr, gt + 1, hi);
}
這種演算法比之前的標準快速排序要快,尤其是對於存在大量重複元素的情況下。該演算法中用到的輔助函式易於實現,如下所示:
void exchange(int arr[],int a, int b) {
int temp;
temp = arr[a];
arr[a] = arr[b];
arr[b] = temp;
}
int compareTo(int a, int b) {
if (a < b)return -1;
if (a > b)return 1;
else return 0;
}
總結
快速排序切分方法的內迴圈會用遞增/遞減的索引將陣列元素與一個定值相比較;快速排序的一個速度優勢在於它的比較次數很少。我們知道排序效率最終還是取決於切分陣列的效果,而這依賴於切分元素的值。如果說快速排序的每一次切分後都可以將陣列對半分的話,那麼它的排序效果是最佳的。翻過來說,該演算法存在的潛在缺點是,在切分不平衡時可能會極為低效。但是,在實際應用當中,出現這種不利情況導致快速排序的執行速度緩慢的概率很低,所以綜上所述,快速排序仍然廣泛應用於計算機程式設計當中。