快速排序
算法概述
從數組中選出一個數作爲主元,然後把數組一分爲二,左邊是小於主元的,右邊是大於主元的。重複這個過程,直到劃分的子集長度爲1
僞碼描述
void Quick_Sort(ElementType[] A, int N) {
pivot = 從數組A中選出一個主元;
將 S = {A[] \ pivot}分成2個獨立子集:
A1 = {小於等於pivot} 和
A2 = {大於等於pivot};
A[] = Quick_Sort(A1, N1) + pivot + Quick_Sort(A2, N2);
}
快速排序算法的最好情況:每次都劃分爲相等的兩部分,因此時間複雜度爲
選主元
- pivot每次取第一個
- 隨機取pivot:
rand()
函數消耗大 - 取頭、中、尾的中位數:比較常用的一種做法
取中位數的實現
// 返回主元的下標
int Median3(ElementType[] A, int Left, int Right) {
int Center = (Left + Right) / 2;
if (A[Left] > A[Center])
Swap(&A[Left], &A[Center]);
if (A[Left] > A[Right])
Swap(&A[Left], &A[Right]);
if (A[Center] > A[Right])
Swap(&A[Center], &A[Right]);
// 以上三次交換實現 A[Left] <= A[Center] <= A[Right]
Swap(&A[Center], &A[Right - 1]); // 將主元藏到右邊
// 在後面比較過程中只需要考慮 A[Left + 1]到A[Right - 2]
return Right - 1;
}
子集劃分
- 從數組左邊開始向右掃描,直到有元素比主元大,然後停下
- 從數組右邊開始向左掃描,直到有元素比主元小,然後停下
- 交換兩個元素後,重複以上步驟,直到左邊下標大於等於右邊下標
- 將主元置於左邊停下的位置
如果有元素正好等於pivot:
- 停下來交換
- 繼續掃描
假設一種極端情況,數組全部由相同元素組成。
- 第一種方案雖然會做多次無用交換,但是最後主元的位置會在中心
- 第二種方案雖然減少了無用交換,但是最後主元位置會在一端
- 所以說,選擇停下來交換,保證主元的位置能靠近中心
小規模數據的處理
- 快速排序的問題
- 使用遞歸消耗比較大
- 對小規模的數據(例如N不到100)可能還不如插入排序快
- 解決方案
- 當遞歸的數據規模充分小,則停止遞歸,直接調用簡單排序(例如插入排序)
- 在程序中定義一個Cutoff的閥值
算法實現
void Quick_Sort(ElementType[] A, int Left, int Right) {
if (Cutoff <= Right - Left) {
PivotIndex = Median3(A, Left, Right);
Pivot = A[PivotIndex];
i = Left;
j = Right - 1;
while (true) {
while (A[++i] < Pivot);
while (A[--j] > Pivot);
if (i < j)
Swap(&A[i], &A[j]);
else break;
}
Swap(&A[i], &A[PivotIndex]);
Quick_Sort(A, Left, i - 1);
Quick_Sort(A, i + 1, Right);
} else
Insertion_Sort(A + Left, Right - Left + 1);
}
void Quick_Sort(ElementType[] A, int N) {
Quick_Sort(A, 0, N - 1);
}
- 穩定性:快速排序是不穩定的
- 平均時間複雜度:
T(N)=O(NlogN)