雙路快速排序法

一、算法出現的背景算法

以前講的,當咱們排序的是一個近乎有序的序列時,快速排序會退化到一個O(n^2)級別的排序算法,數組

而對此的改進就是引入了隨機化快速排序算法;可是當咱們排序的是一個數值重複率很是高的序列時,函數

此時隨機化快速排序算法就再也不起做用了,而將會再次退化爲一個O(n^2)級別的排序算法,那爲何性能

會出現這種狀況呢?且聽下面的分析:測試

 

如上圖所示就是以前分析的快速排序算法的partition的操做原理,咱們經過判斷此時i索引指向的數組ui

元素e>v仍是<v,將他放在橙色或者是紫色兩個不一樣的位置,而後將整個數組分紅兩個部分遞歸下去;spa

可是這裏其實咱們是沒有考慮=v的狀況,其實隱含的意思就是下面的兩種狀況:3d

 

         

其實從這裏就能夠看出來了,無論是>=v仍是<=v,當咱們的序列中存在大量重複的元素時,code

排序完成以後就會將整個數組序列分紅兩個極度不平衡的部分,因此又退化到了O(n^2)級別blog

的時間複雜度,這是由於對於每個"基準"元素來講,重複的元素太多了,若是咱們選的"基準"

元素稍微有一點的不平衡,那麼就會致使兩部分的差距很是大;即時咱們的"基準"元素選在了

一個平衡的位置,可是因爲等於"基準"元素的元素也很是多,也會使得序列被分紅兩個及其不平

衡的部分,那麼在這種狀況下快速排序就又會退化成O(n^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--開始遍歷後後面的元素

 

三、雙路快速排序算法的實現(基於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 /*********************************************************************************************/

 

四、性能測試

通常序列:

 

近乎有序的序列:

 

重複率很是高的序列:

相關文章
相關標籤/搜索