歸併排序算法

一、什麼是歸併排序?算法

歸併排序是利用遞歸和分而治之的技術將數據序列劃分紅爲愈來愈小的序列,將兩個(或兩個以上)有序子序列合併成一個新的有序序列,即把待排序序列分爲若干個子序列,每一個子序列是有序的,而後再把有序子序列合併爲一個新的有序序列,最終將整個序列變得有序。數組

時間複雜度: O(nlogn)函數

 

二、效果演示性能

  這個序列爲待排序序列測試

 

  將這個序列分紅2個子序列,分別對它們進行排序,再合併爲一個新的有序序列優化

 

  同理在對上面的兩個子序列排序的時候,也會將他們各自分爲2個子序列,分別動畫

                                            對它們進行排序,再合併spa

 

   繼續沿用上面的思想,再對它們進行分半,此時每個子序列只有一個元素了,code

                                             他們就是有序的blog

 

  因此咱們此時要作的就是向上合併

 

     

 

 

 

  當排序合併到最後一層的時候,整個序列就變得有序了,至此排序完畢

 

三、動畫演示

 

 

四、算法的實現(基於C++)

程序中使用了3個變量分別指向以下所示的位置

    

紅色的箭頭表示最終在歸併的過程當中須要跟蹤的位置,兩個藍色的箭頭表示已經排好序的兩個子序列中當前須要考慮的元素

 1 /************************************* 歸併排序算法實現 **********************************/
 2 /* 將arr[left, mid]和arr[mid+1, right]兩部分進行歸併 */
 3 template<typename T>
 4 void __merge (T arr[], int left, int mid, int right)
 5 {
 6     T *temp = new T[right - left + 1];  // 申請分配堆內存(這個地方仍是建議不要使用棧,由於若是數據量太大可能會致使棧溢出)
 7     if (NULL == temp) {                 // 判斷申請是否成功
 8         return;
 9     }
10 
11     for (int i = left; i <= right; i++) { // 給臨時空間賦值(注意他們之間的一個偏移量)
12         temp[i - left] = arr[i];
13     }
14 
15     /* 如下就是對兩個子序列進行向上歸併的操做 */
16     int i = left, j = mid + 1;            // i指向第一部分的起始位置,j指向第二部分的起始位置
17     for (int k = left; k <= right; k++) { // k指向當前須要排序的位置
18         if (i > mid) {                    // 判斷第一部分中的數據是否已經所有歸位了
19             arr[k] = temp[j - left];
20             j++;
21         }
22         else if (j > right) {             //  判斷第二部分中的數據是否已經所有歸位了
23             arr[k] = temp[i - left];
24             i++;
25         }
26         else if (temp[i - left] > temp[j - left]) {// 若是第二部分中對應的數據更小,則將它放在須要排序的位置中
27             arr[k] = temp[j - left];
28             j++;
29         }
30         else {
31             arr[k] = temp[i - left];  // 若是是第一部分中對應的數據更小,則將它放在須要排序的位置中
32             i++;
33         }
34     }
35 
36     delete[] temp;         // 釋放堆內存空間
37 }
38 
39 /* 對arr[left, right]進行直接插入排序 */
40 template<typename T>
41 void __insertSortMG (T arr[], int left, int right)
42 {
43     for (int i = left + 1; i <= right; i++) {
44         int j;
45         T temp = arr[i];
46 
47         for (j = i; j > left && arr[j - 1] > temp; j--) {
48             arr[j] = arr[j - 1];
49         }
50 
51         arr[j] = temp;
52     }
53 }
54 
55 /* 遞歸使用歸併排序,對arr[left....right]範圍進行排序 */
56 template<typename T>
57 void __mergeSort (T arr[], int left, int right)
58 {
59     int mid = (left + right) / 2;     // 找出兩部分的分界點位置    
60 
61     __mergeSort<T>(arr, left, mid);      // 對第一部分子序列遞歸調用該函數
62     __mergeSort<T>(arr, mid + 1, right); // 對第二部分子序列遞歸調用該函數
63     
64     __merge<T>(arr, left, mid, right);    //  對兩部分數據進行歸併操做
65 }
66 
67 template<typename T>
68 void mergeSort (T arr[], int count)
69 {
70     __mergeSort<T>(arr, 0, count - 1);     //  對arr[0...count-1]範圍進行歸併排序 
71 }
72 /******************************************************************************************/

 

五、算法的性能測試

將歸併排序算法與前面講的兩種排序算法進行一個時間上的測試:

測試數據量50000:

 

測試數據量100000:

 

六、歸併排序算法的優化

(1)第一種優化方法(在__mergeSort函數中進行優化):

 1 /* 遞歸使用歸併排序,對arr[left....right]範圍進行排序 */
 2 template<typename T>
 3 void __mergeSort (T arr[], int left, int right)
 4 {
 5     if (left >= right) {           // 遞歸終止的判斷條件
 6         return;
 7     }
 8 
 9     int mid = (left + right) / 2;   //  將數組分紅2部分的中間索引值    
10 
11     __mergeSort<T>(arr, left, mid);      // 對第一部分子序列遞歸調用該函數
12     __mergeSort<T>(arr, mid + 1, right); // 對第二部分子序列遞歸調用該函數
13     
14     if (arr[mid] > arr[mid + 1]) {       // 優化措施1: 適用於自己有序程度很高的序列
15         __merge<T>(arr, left, mid, right);    //  將兩部分數據進行歸併操做
16     }
17         
18 }

咱們須要作的就是在調用__merge函數以前進行一個判斷,判斷第二個子序列的首元素是否大於第

一個子序列的最後一個元素,若是確實要大或者相等,則不須要調用__merge函數來進行後續的操

做了,由於這兩個子序列合在一塊兒自己就是一個有序的序列了,這個特性歸結于歸並排序自己的各個

子序列已經就是有序的狀態了、如此一來就能夠省去必定的時間開銷,尤爲是在序列自己的有序程度

高的時候,這個效果越可以體現出來。須要說明的是,若是你的數組序列自己有序程度很是低,建議

就不要加這個優化,畢竟條件判斷也是須要消耗必定的時間的。

 

(2)第二種優化方法(在__merge函數中進行優化)

 1 /* 對arr[left, right]進行直接插入排序 */
 2 template<typename T>
 3 void __insertSortMG(T arr[], int left, int right)
 4 {
 5     for (int i = left + 1; i <= right; i++) {
 6         int j;
 7         T temp = arr[i];
 8 
 9         for (j = i; j > left && arr[j - 1] > temp; j--) {
10             arr[j] = arr[j - 1];
11         }
12 
13         arr[j] = temp;
14     }
15 }
16 
17 /* 遞歸使用歸併排序,對arr[left....right]範圍進行排序 */
18 template<typename T>
19 void __mergeSort (T arr[], int left, int right)
20 {
21     if (right - left <= 40) {    // 優化措施2: 對小數據量排序使用直接插入排序的方法
22         __insertSortMG<T>(arr, left, right);
23         return;           
24     }
25 
26     int mid = (left + right) / 2;   //  將數組分紅2部分的中間索引值    
27 
28     __mergeSort<T>(arr, left, mid);      // 對第一部分子序列遞歸調用該函數
29     __mergeSort<T>(arr, mid + 1, right); // 對第二部分子序列遞歸調用該函數
30     
31     __merge<T>(arr, left, mid, right);    //  將兩部分數據進行歸併操做
32 }

爲何須要這麼作,理由有2個:

a、當數據量比較小的時候,序列的有序性強,那麼使用插入排序會有優點。

b、雖然插入排序的時間複雜度是O(n^2),而歸併排序的時間複雜度是O(nlogn),可是前面是存在

一個係數的,而插入排序的係數實際上是要小於歸併排序的係數,當在數據量較小的時候,每每總體的

時間複雜度仍是插入排序的小。這種優化方法幾乎在全部的高級算法中都是可使用的。那咱們再來

看看,使用了優化方式2以後的性能指示:

測試數據量50000:

 

測試數據量100000:

 

至於效果有多大,你們與上面的比較就能知道了,好了今天的歸併排序暫時先告一段落了。

相關文章
相關標籤/搜索