一看就懂 ! 图解归并排序

共 1391字,需浏览 3分钟

 ·

2021-11-22 15:37

大家好,我是bigsai,我们今天来谈谈归并排序算法。
归并排序(Merge Sort)是建立在归并操作上的一种有效,稳定的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。

一、算法思想

归并排序的主要思想是分治法。主要过程是:


1. 将n个元素从中间切开,分成两部分。


2. 将剩下的数组通过递归的方式一直分割,直到数组的大小为 1,此时只有一个元素,那么该数组就是有序的了。


3. 从最底层开始逐步合并两个排好序的数列。把两个数组大小为1的合并成一个大小为2的有序数列,再把两个大小为2有序数列的合并成4的有序数列 … 直到全部小的数组合并起来。

二、思考

那么如何将两个有序数列合成一个有序的数列呢?


我们举个栗子把,一看就懂啦f6f873ddb60923bca0e9c50958856912.webpf6f873ddb60923bca0e9c50958856912.webpf6f873ddb60923bca0e9c50958856912.webp

三、举个栗子

例如有数组 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]。
751bfc3a8b7560b697f87ffe30aa880c.webp
第一步:
把这两个小的数组拆分为 left 数组和 right 数组。如下图所示,使用 i 指向 left 的第一个元素, 使用 j 指向 right 的第一个元素。
ad6891609b320fa23e292708156f582d.webp
第二步:
建立一个空数组 arr ,使用 k 指向数组第一个元素。
f1a4090f6381984264430fcf9fa6e04b.webp


第三步:
比较 i 和 j 所指数字,将小的数字放在 k 所指位置。同时将小的数字所指位置和 k 所指位置向右移一位。
2 < 3 , 将 2 填入 arr 数组 ,同时右移 j 和 k。
629a73b36be51ef3fd1573b908ea1fd2.webp


3 < 4 , 将 3 填入 arr 数组 ,同时右移 i 和 k。
fd1d43e99edd2cb9ec9634315d12cbc3.webp


4 < 7,将 4 填入 arr 数组,同时右移 j 和 k。
4c851a2e906f2ddbad20c88c7877e375.webp


6 < 7,将 6 填入 arr 数组,同时右移 j 和 k。
784f273bfa31d26613ad95f7cc0869d6.webp


7 < 9,将 7 填入 arr 数组,同时右移 i 和 k。
cebe0b98eeaaa505b5fc1d5ec9b162b4.webp
8 < 9,将 8 填入 arr 数组,同时右移 i 和 k。

07e913229a13f09ffb4c46f2a32335b0.webp


10 > 9,将 9 填入 arr 数组,同时右移 j 和 k。
d48cf4f772b7f98db96fefc1f2211975.webp


可以发现此时 right 数组已经填完了,所以此时只需要把 left 数组剩下的数字填入 arr 即可。
de9d80b96d5943d3454a9291012c80cd.webp
一顿操作猛如虎,这样就把两个有序的数组通过归并的方式排好顺序啦,是不是很赞。
那么问题来了,难道归并排序只能排这种有序的数组么?

那出现一个无序的数组该咋办呢?例如这个数组现在变为 arr [8,7,2,10,3,9,4,6];

四、问题解决

此刻需要运用分治(divide-and-conquer)策略(分治法将问题分(divide)成一些小的问题然后递归求解,而治(conquer)的阶段则将分的阶段得到的各答案"修补"在一起,即分而治之)。
其实上面第三部分就是治(conquer)的过程,将两个有序的序列合成为一个有序的序列。

小栗子:图解无序序列进行希尔排序。

7e50dbcc7ef2c1c1e3eaff1503f76888.webp


五、算法实现

#include 

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     left[i-L] = arr[i];
   }
   
  // 填充右边的数组
   for (i=M; i<=R; i++){
     right[i-M] = arr[i];
   }
   
// for (int i=0; i
// printf("%d\n",left[i]);
// }
//
// for (int i=0; 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]);
  }
}


输出:
1b9b1562356348ce6a8a40093bd9bab8.webp

六、算法分析

时间复杂度O(nlogn)。
空间复杂度O(N),归并排序需要一个与原数组相同长度的数组做辅助来排序。
稳定性:稳定因为交换元素时,可以在相等的情况下做出不移动的限制,所以归并排序是可以稳定的。


七、适用场景

归并排序需要一个跟待排序数组同等空间的临时数组,因此,使用归并排序时需要考虑是否有空间上的限制。如果没有空间上的限制,归并排序是一个不错的选择。8f9fabd679fe8c3d31517bc5e700c35c.webp关注下方公众号,分享硬核知识


浏览 112
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报