1、算法出現的背景
之前講的,當我們排序的是一個近乎有序的序列時,快速排序會退化到一個O(n^2)級別的排序算法,
而對此的改進就是引入了隨機化快速排序算法;但是當我們排序的是一個數值重復率非常高的序列時,
此時隨機化快速排序算法就不再起作用了,而將會再次退化為一個O(n^2)級別的排序算法,那為什么
會出現這種情況呢?且聽下面的分析:
如上圖所示就是之前分析的快速排序算法的partition的操作原理,我們通過判斷此時i索引指向的數組
元素e>v還是<v,將他放在橙色或者是紫色兩個不同的位置,然后將整個數組分成兩個部分遞歸下去;
但是這里其實我們是沒有考慮=v的情況,其實隱含的意思就是下面的兩種情況:
其實從這里就可以看出來了,不管是>=v還是<=v,當我們的序列中存在大量重復的元素時,
排序完成之后就會將整個數組序列分成兩個極度不平衡的部分,所以又退化到了O(n^2)級別
的時間復雜度,這是因為對於每一個"基准"元素來說,重復的元素太多了,如果我們選的"基准"
元素稍微有一點的不平衡,那么就會導致兩部分的差距非常大;即時我們的"基准"元素選在了
一個平衡的位置,但是由於等於"基准"元素的元素也非常多,也會使得序列被分成兩個及其不平
衡的部分,那么在這種情況下快速排序就又會退化成O(n^2)級別的排序算法。如何解決呢?
這就要用到今天講的雙路快速排序算法的原理了。
2、雙路快速排序算法的原理
之前說的快速排序算法是將>v和<v兩個部分元素都放在索引值i所指向的位置的左邊部分,而我們
的雙路快速排序算法則不同,他使用兩個索引值(i、j)用來遍歷我們的序列,將<v的元素放在索
引i所指向位置的左邊,而將>v的元素放在索引j所指向位置的右邊,這也正是雙路排序算法的
partition原理:
基本思想:
首先從左邊的i索引往右邊遍歷,如果i指向的元素<v,
那直接將i++移動到下一個位置,直道i指向的元素>=v則停止
然后使用j索引從右邊開始往左邊遍歷,如果j指向的元素>v,
那直接將j--移動到下一個位置,直道j指向的元素<=v則停止
此時i之前的元素都已經歸並為<v的部分了,而j之后的元素也
都已經歸並為>v的部分了,此時只需要將arr[i]和arr[j]交換位置即可
這樣就可以避免出現=v的元素全部集中在某一個部分,這正
是雙路排序算法的一個核心
將i++,j--開始遍歷后后面的元素
3、雙路快速排序算法的實現(基於C++)
1 /******************************** 雙路快速排序算法實現 ***************************************/ 2 template<typename T> 3 T __partition2 (T arr[], int left, int right) 4 { 5 std::swap(arr[left], arr[std::rand() % (right - left + 1) + left]); // 隨機產生"基准"元素所在位置,並與第一個元素交換位置 6 T v = arr[left]; // 將第一個元素作為"基准"元素 7 8 // 使用i索引從左到右遍歷,使用j索引從右到左遍歷 9 int i = left + 1; // 索引值i初始化為第二個元素位置 10 int j = right; // 索引值j初始化為最后一個元素位置 11 while (true) { 12 while ((i < right) && (arr[i] < v)) i++; // 使用索引i從左往右遍歷直到 arr[i] < v 13 while ((j > left + 1) && (arr[j] > v)) j--; // 使用索引j從右往左遍歷直到 arr[j] > v 14 if (i >= j) break; // 退出循環的條件 15 std::swap(arr[i], arr[j]); // 將 arr[i] 與 arr[j] 交換位置 16 i++; // i++ j-- 17 j--; 18 } 19 20 std::swap(arr[left], arr[j]); // 最后將"基准"元素v放置到合適的位置 21 22 return j; 23 } 24 25 template<typename T> 26 void __quickSort2 (T arr[], int left, int right) 27 { 28 if (right - left <= 40) { // 當遞歸到序列數據量較小時使用插入排序算法 29 __insertSortMG<T>(arr, left, right); 30 return; 31 } 32 33 int p = __partition2<T>(arr, left, right); // 對arr[left...right]區間元素進行partition操作,找到"基准"元素 34 __quickSort2<T>(arr, left, p - 1); // 對基准元素之前的序列遞歸調用__quickSort函數 35 __quickSort2<T>(arr, p + 1, right); // 對基准元素之后的序列遞歸調用__quickSort函數 36 } 37 38 template<typename T> 39 void quickSort2 (T arr[], int count) 40 { 41 std::srand(std::time(NULL)); // 種下隨機種子 42 __quickSort2<T>(arr, 0, count - 1); // 調用__quickSort函數進行快速排序 43 } 44 /*********************************************************************************************/
4、性能測試
一般序列:
近乎有序的序列:
重復率非常高的序列: