快速排序基本思想是,對待排序序列進行划分(Partition),一次划分,選擇一個元素作為樞軸,然后將所有比樞軸小的元素放到樞軸的左邊,將比樞軸大的元素放到樞軸的右邊。然后對該樞軸划分的左右子序列分別再進行划分,如此遞歸。Partition是一個非常重要的概念,因為它只需要O(n)的時間復雜度就可以將待排序序列划分為兩塊具有大小關系的區間,可以根據這一特性求解待排序序列中最大的k個數、第k大的數等類似問題。
快速排序算法復雜度O(nlogn).
就平均時間而言,快速排序是目前被認為是最好的一種內部排序方法,其平均時間是O(nlogn),最壞情況是O(n^2),最壞的情況就是如下倒序完后再正序排的情況。
C++代碼如下:
#include "stdafx.h" #define MAXSIZE 20 typedef struct{ int r[MAXSIZE+1]; int len; }SqList; int Partition(SqList &L, int low, int high) { L.r[0] = L.r[low]; // 以第一個元素作為樞軸 int pivotkey = L.r[low];// 記錄樞軸關鍵字 while (low < high) { while(low<high && L.r[high]>=pivotkey)
--high;// 找到從high位置開始向前第一個比樞軸小的元素 L.r[low] = L.r[high];// 將找到的比樞軸小的元素放到前邊的空閑位置
while(low<high && L.r[low]<=pivotkey)
++low;// 找到從low位置開始向后第一個比樞軸大的元素 L.r[high] = L.r[low];// 將找到的比樞軸大的元素放到后邊的空閑位置 } L.r[low] = L.r[0];// 將樞軸放回中間的空閑位置,由while{}循環可知,low最后空閑 return low; } void QSort(SqList &L, int low, int high) { if (low < high) { int pivotloc = Partition(L, low, high); QSort(L, low, pivotloc-1); QSort(L, pivotloc+1, high); } } int _tmain(int argc, _TCHAR* argv[]) { SqList sqList; for (int i=1; i<MAXSIZE+1; i++) { sqList.r[i] = MAXSIZE - i; } sqList.len = MAXSIZE; QSort(sqList, 1, sqList.len); return 0; }
// 附一次划分過程,第一行為index,第二行為待排序序列
// 0 1 2 3 4 5 6 7
// __ 49 38 65 97 76 13 27 // 開始時,0號位空閑(low:1, high:7)
// 49 __ 38 65 97 76 13 27 // 將第1號元素作為樞軸,放到0號位,1號位冗余(low:1, high:7)
// 49 27 38 65 97 76 13 __ // 發現27比49小,將7號位值放到1號位,7號位冗余(low:1, high:7)
// 49 27 38 __ 97 76 13 65 // 發現65比49大,將3號位值放到7號位,3號位冗余(low:3, high:7)
// 49 27 38 13 97 76 __ 65 // 發現13比49小,將6號位值放到3號位,6號位冗余(low:3, high:6)
// 49 27 38 13 __ 76 97 65 // 發現97比49大,將4號位值放到6號位,4號位冗余(low:4, high:5)
// __ 27 38 13 49 76 97 65 // low == high,將樞軸放到4號位,此時49左邊的都比49小,右邊的都比49大
STL的所有關系型容器都擁有自動排序的功能(底層采用RB-tree),所以不需要sort算法。序列式容器的中的stack、queue等都有特別的出入口,不允許用戶對元素進行排序。剩下的vector、deque和list,前兩者的迭代器屬於RandomAccessIterators,適合sort算法。泛型算法一定要求迭代器是RandomAccessIterators。因為任何一個元素都可以被選作樞軸(pivot),但是其合適與否卻會影響Quick Sort的效率。為了避免樞軸不夠隨機帶來的惡化效應,最理想的方式是取整個序列的頭、尾、中央三個位置的元素,以其中值作為樞軸,這種做法成為三點中值(Median-of-Three),為了能夠快速取出中央位置的元素,顯然迭代器必須能夠隨機定位,因此快速排序的泛型算法中迭代器必須是RandomAccessIterators。
STL的sort算法,數據量大時采用Quick Sort,分段遞歸排序。一旦分段后的數據量小於某個門檻,為避免Quick Sort的遞歸調用帶來過大的額外負擔,就改用Insertion Sort。Insertion Sort雖然時間復雜度是O(n^2),但是當數據量很小時,卻有不錯的效果。另外雖然STL有三點中值來防止樞軸選取不當的問題,還有introsort進行自我偵測,如果分割行為有惡化傾向時,會轉而改用Heap Sort。
