雙路快速排序法


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、性能測試

一般序列:

 

近乎有序的序列:

 

重復率非常高的序列:

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM