本文作者華科小濤:@http://www.cnblogs.com/hust-ghtao/,參考《算法導論》,代碼借用《劍指offer》
快速排序是一種最壞情況時間復雜度為
的排序算法。雖然最壞情況的時間復雜度很差,在在實際應用中是最好的選擇,平均性能很好:期望時間復雜度
,而且
隱含的常數因子非常小。另外,它還能夠進行原排序,在虛擬環境中也能很好工作。基於隨機抽樣的快速排序算法,在輸入元素互異的情況下,期望運行時間為
。
1.基本思想
快速排序利用了分治策略。分治策略可以分為3個步驟:
- 分解:將問題划分為一些子問題,子問題的形式與原問題一樣,只是規模更小。
- 解決:遞歸的求解出子問題。如果子問題的規模足夠小,則停止遞歸,直接求解。
- 合並:將子問題的解組合成原問題的解。
對一個典型的子數組A[p..r]進行快速排序的分治過程如下:
- 分解:數組A[p..r]被划分為兩個(可能為空)子數組A[p..q-1]和A[q+1..r],使得A[p..q-1]中的每一個元素都小於等於A[q],而A[q+1..r]中的每個元素都大於A[q]。其中計算下標q也是划分過程的一部分。
- 解決:通過遞歸調用快速排序,對子數組A[p..q-1]和A[q+1..r]進行排序。
- 合並:因為子數組都是原址排序的,所以不需要合並操作。
2.詳細過程
快速排序的偽代碼如下:
,為了排序數組A的全部元素,初始調用QUICKSORT(A, 1, A.length)。
其中最關鍵的部分就是數組的划分PARTITION,它實現了對子數組A[p..r]的原址重排。偽代碼如下:
這里的PARTITION程序選擇x=A[r]作為主元,並圍繞着它來划分數組。
隨着程序的增加,數組被划分成4個區域,如下圖所示:
其中:
- A[p..i]上的所有值都小於等於x;
- A[i+1..j-1]區間的所有值都大於x;
- A[j..r-1]是還未掃描的元素,可能屬於任何一種情況;
- A[r]=x。
指針 i 一直指向小值數組的最后一個元素,j指向大值數組末尾的下一個元素。
PARTITION的一次迭代過程中會出現兩種情況:
(a)如果A[j]>x,需要做的只是將j值加1:
(b)A[j]<=x,則將i值加1,並交換A[i]和A[j],在將j值加1,使循環不變量保持不變。
在PARTITION的最后,將主元與最左的大於x的元素進行交換,就可以將主元移動到它在數組中的正確位置,並返回主元的新下標。
3.性能分析
快速排序的運行時間依賴於划分是否平衡,而平衡與否又依賴於用於划分的元素。
3.1 最壞情況划分
當划分產生的兩個子問題分別包含了n-1個元素和0個元素,這是極不平衡的划分。假設算法的每一次遞歸都出現了這種不平衡的划分,算法運行時間的遞歸式可以表示為:
所以,如果在算法的每一層遞歸上,划分都是最大程度不平衡的,那么算法的時間復雜度為:。
3.2 平均情況
在最平衡的划分中,PARTITION得到的兩個子問題的規模都不大於n/2。算法運行時間的遞歸式為:
可以解得:
另外,只要是划分是常數比例的,甚至好的和差(極不平衡)的划分交替出現時,快速排序算法和全是好的划分時一樣,仍然是。
4.隨機化版本
在算法中引入隨機性,使得算法對所有的輸入都能獲得較好的期望性能。
從A[p..r]中隨機選擇一個元素作為主元。為了達到這一目的,首先將A[r]與從A[p..r]隨機選擇的元素交換。通過對序列p..r隨機抽樣保證主元素
x=A[r]是等概率從r-p+1個元素中選取的。
下面是RANDOMIZED-PARTITION和RANDOMIZED-QUICKSORT的偽代碼:
在使用RANDOMIZED-PARTITION,輸入元素互異的情況下,快速排序算法的期望運行時間為。
6.代碼實現
RANDOMIZED-PARTITION:
1: int Partition(int data[], int length, int start, int end)
2: {
3: if (data == NULL||length<=0||start<0||end>=length)
4: {
5: throw new std::exception("Invalid Parameters");
6: }
7: int index = RandomInRange(start, end);
8: Swap(&data[index], &data[end]);
9:
10: int small = start - 1;
11: for (index = start; index < end;++index)
12: {
13: if (data[index] < data[end])
14: {
15: ++small;
16: if (small!=index)
17: {
18: Swap(&data[index], &data[small]);
19: }
20: }
21: }
22: ++small;
23: Swap(&data[small], &data[end]);
24:
25: return small;
26: }
QUICKSORT:
1: void QuickSort(int data[], int length, int start, int end)
2: {
3: if (start == end)
4: {
5: return;
6: }
7: int index = Partition(data, length, start, end);
8: if (index >start)
9: {
10: QuickSort(data, length, start, index-1);
11: }
12: if (index<end)
13: {
14: QuickSort(data, length, index + 1, end);
15: }
16: }