歸併排序,我舉個例子你就看懂了

華為雲開發者社群發表於2021-11-27
摘要:歸併排序(Merge Sort)是建立在歸併操作上的一種有效,穩定的排序演算法,該演算法是採用分治法(Divide and Conquer)的一個非常典型的應用。

本文分享自華為雲社群《一看就懂 ! 圖解歸併排序》,作者: bigsai 。

歸併排序(Merge Sort)是建立在歸併操作上的一種有效,穩定的排序演算法,該演算法是採用分治法(Divide and Conquer)的一個非常典型的應用。

一、演算法思想

歸併排序的主要思想是分治法。主要過程是:

1. 將n個元素從中間切開,分成兩部分。

2. 將剩下的陣列通過遞迴的方式一直分割,直到陣列的大小為 1,此時只有一個元素,那麼該陣列就是有序的了。

3. 從最底層開始逐步合併兩個排好序的數列。把兩個陣列大小為1的合併成一個大小為2的有序數列,再把兩個大小為2有序數列的合併成4的有序數列 … 直到全部小的陣列合並起來。

二、思考

那麼如何將兩個有序數列合成一個有序的數列呢?

我們舉個例子把,一看就懂啦。

三、舉個例子

例如有陣列 arr [3,7,8,10,2,4,6,9]; 我們可以把這個陣列分成兩個有序的子序列。

分別為 [3, 7, 8, 10] 和 [2, 4, 6, 9],並將其合併為有序序列[2,3,4,6,7,8,9,10]。

歸併排序,我舉個例子你就看懂了

第一步:

把這兩個小的陣列拆分為 left 陣列和 right 陣列。如下圖所示,使用 i 指向 left 的第一個元素, 使用 j 指向 right 的第一個元素。

歸併排序,我舉個例子你就看懂了

第二步:

建立一個空陣列 arr ,使用 k 指向陣列第一個元素。

歸併排序,我舉個例子你就看懂了

第三步:

比較 i 和 j 所指數字,將小的數字放在 k 所指位置。同時將小的數字所指位置和 k 所指位置向右移一位。

2 < 3 , 將 2 填入 arr 陣列 ,同時右移 j 和 k。

歸併排序,我舉個例子你就看懂了

3 < 4 , 將 3 填入 arr 陣列 ,同時右移 i 和 k。

歸併排序,我舉個例子你就看懂了

4 < 7,將 4 填入 arr 陣列,同時右移 j 和 k。

歸併排序,我舉個例子你就看懂了

6 < 7,將 6 填入 arr 陣列,同時右移 j 和 k。

歸併排序,我舉個例子你就看懂了

7 < 9,將 7 填入 arr 陣列,同時右移 i 和 k。

歸併排序,我舉個例子你就看懂了

8 < 9,將 8 填入 arr 陣列,同時右移 i 和 k。

歸併排序,我舉個例子你就看懂了

10 > 9,將 9 填入 arr 陣列,同時右移 j 和 k。

歸併排序,我舉個例子你就看懂了

可以發現此時 right 陣列已經填完了,所以此時只需要把 left 陣列剩下的數字填入 arr 即可。

歸併排序,我舉個例子你就看懂了

一頓操作猛如虎,這樣就把兩個有序的陣列通過歸併的方式排好順序啦,是不是很贊。


那麼問題來了,難道歸併排序只能排這種有序的陣列麼?

那出現一個無序的陣列該咋辦呢?例如這個陣列現在變為 arr [8,7,2,10,3,9,4,6];

四、問題解決

此刻需要運用分治(divide-and-conquer)策略(分治法將問題分(divide)成一些小的問題然後遞迴求解,而治(conquer)的階段則將分的階段得到的各答案"修補"在一起,即分而治之)。

其實上面第三部分就是治(conquer)的過程,將兩個有序的序列合成為一個有序的序列。

小栗子:圖解無序序列進行希爾排序。

歸併排序,我舉個例子你就看懂了

五、演算法實現

    #include <stdio.h>
       void merge(int arr[], int L, int M, int R) {
         int LEFT_SIZE = M - L;
         int RIGHT_SIZE = R - M + 1;
         int left[LEFT_SIZE];
         int right[RIGHT_SIZE];
         int i, j, k;
         // 填充左邊的陣列
          for (i=L; i<M; i++){
            left[i-L] = arr[i];
          }
         // 填充右邊的陣列
          for (i=M; i<=R; i++){
            right[i-M] = arr[i];
          }
       // for (int i=0; i<LEFT_SIZE; i++){
       // printf("%d\n",left[i]);
       // }
       // 
       // for (int i=0; i<RIGHT_SIZE; i++){
       // printf("%d\n",right[i]);
       // }
         // 合併陣列
          i = 0; j = 0; k = L;
          while (i < LEFT_SIZE && j < RIGHT_SIZE){
            if (left[i] < right[j]){
              arr[k] = left[i];
              i++;
              k++;
            }else{
              arr[k] = right[j];
              j++;
              k++;
            }
          }
          while(i < LEFT_SIZE){
            arr[k] = left[i];
            i++;
            k++;
          }
          while(j < RIGHT_SIZE){
            arr[k] = right[j];
            j++;
            k++;
          }
       }
       void mergeSort(int arr[], int L, int R){
         if (L == R){
           return;
         }else{
           int M = (L + R) / 2;
           mergeSort(arr,L,M);
           mergeSort(arr, M+1,R);
           merge(arr, L, M+1,R);
         }
       }
       int main(){
       // int arr[] = {3,7,8,10,2,4,6,9};
         int arr[] = {8,7,2,10,3,9,4,6};
         int L = 0;
         int M = 4;
         int R = 7;
         mergeSort(arr,L,R);
         for (int i=0; i<=R; i++){
           printf("%d\n",arr[i]);
         }
       }

輸出:

歸併排序,我舉個例子你就看懂了

六、演算法分析

時間複雜度:O(nlogn)。

空間複雜度:O(N),歸併排序需要一個與原陣列相同長度的陣列做輔助來排序。

穩定性:穩定,因為交換元素時,可以在相等的情況下做出不移動的限制,所以歸併排序是可以穩定的。

七、適用場景

歸併排序需要一個跟待排序陣列同等空間的臨時陣列,因此,使用歸併排序時需要考慮是否有空間上的限制。如果沒有空間上的限制,歸併排序是一個不錯的選擇。

 

點選關注,第一時間瞭解華為雲新鮮技術~

相關文章