当前位置:   article > 正文

图解归并排序,带你彻底了解清楚!_归并排序算法

归并排序算法

写在前面:

大家好,我是时光。

今天给大家带来的是排序算法中的归并排序,这种排序需要拆分归并。 我采用图解方式讲解,争取写透彻。话不多说,开始!

思维导图:

归并排序思维导图

归并排序思维导图

1,归并排序的概念

1.1,算法介绍

选择排序一样,归并排序的性能不受输入数据的影响,但表现比选择排序好的多,因为始终都是O(n log n)时间复杂度。代价是需要额外的内存空间。归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。

1.2,基本思想

基本思路就是将数组分成二组 A,B,如果这二组组内的数据都是有序的,那么就可以 很方便的将这二组数据进行排序。如何让这二组组内数据有序了?

可以将 A,B 组各自再分成二组。依次类推,当分出来的小组只有一个数据时,可以认为这个小组组内已经达到了有序,然后再合并相邻的二个小组就可以了。这样通过先递归的分解数列,再合并数列就完成了归并排序。

1.3,归并的定义

将两个或两个以上的有序序列合并成一个新的有序序列:

2路归并

2路归并

3路归并:将3个有序序列归并为一个新的有序序列,称为3路归并;

多路归并:将多个有序序列归并为一个新的有序序列;,称为多路归并;

1.4,如何合并有序序列

只要从比较两个数列的第一个数,谁小就先取谁,取了后就在对应数列中删除这个数。然后再进行比较,如果有数列为空,那直接将另一个数列的数据一次取出即可。

2,算法思路

我们先搞清楚这个归并排序思想,先把逻辑搞清楚,不着急写代码。

我们首先有一个无序数组,比方说

int[] arr={4,2,8,0,5,7,1,3,9};

2.1,第一个步骤,拆分数组

我们采用2路归并,(start+end)/2处为临界点,将初始无序数组拆分成两个无序序列,然后依次循环拆分,最后再归并为一个新的有序序列;

拆分过程

拆分过程

首先我们来思考下判断条件,当start=end时,此时序列只有一个元素,所以这是临界点; 然后就是分组,拆分,采用递归的方式。最后再把所有拆分好的元素进行归并!

拆分代码:

  1. public static int[] Merge_Sort(int[] arr, int start, int end) {
  2.         //start==end时,此时分组里只有一个元素,所以这是临界点
  3.         if (start < end) {
  4.             //分成左右两个分组,再进行递归
  5.             int mid = (start + end/ 2;
  6.             //左半边分组
  7.             Merge_Sort(arr, start, mid);
  8.             //右半边分组
  9.             Merge_Sort(arr, mid + 1end);
  10.             //递归之后再归并归并一个大组
  11.             Merge(arr, start, mid, end);
  12.         }
  13.         return arr;
  14.     }

拆分到最后一步,每一个小序列都是单独的元素,在第二步我们再把这些元素进行归并。

2.2,第二个步骤,归并序列

拆完以后,我们需要将这些单独的元素放入一个额外空间储存,再进行排序。因此,归并排序是需要占用额外空间的。

我们这里以第一次拆分的两个分组为例,其实也就是归并的最后一步。第一次拆分之后的两个序列就是[4 2 8 0 5][7 1 3 9]了;但是大家要注意,我们在对这两个序列进行归并时,他们应该是早就排好序了!因为是递归拆分,在最后一次拆分的时候序列都是单独的元素,所以到归并的最后一步时,也就是左边的序列应该是[0 2 4 5 8],右边的序列应该是[1 3 7 9]

归并过程

归并过程

归并代码:

  1. //归并方法
  2. public static void Merge(int[] arr, int start, int mid, int end) {
  3.     //左边分组的起点i_start,终点i_end,也就是第一个有序序列
  4.     int i_start = start;
  5.     int i_end = mid;
  6.     //右边分组的起点j_start,终点j_end,也就是第二个有序序列
  7.     int j_start = mid + 1;
  8.     int j_end = end;
  9.     //额外空间初始化
  10.     int[] temp=new int[end-start+1];
  11.     int len = 0;
  12.     //合并两个有序序列
  13.     while (i_start <= i_end && j_start <= j_end) {
  14.         //当arr[i_start]<arr[j_start]值时,将较小元素放入额外空间,反之一样
  15.         if (arr[i_start< arr[j_start]) {
  16.             temp[len] = arr[i_start];
  17.             len++;
  18.             i_start++;
  19.         } else {
  20.             temp[len] = arr[j_start];
  21.             len++;
  22.             j_start++;
  23.         }
  24.     }
  25. }

这里有个代码小技巧,注意上面合并有序序列的这个循环里面的代码,其实只要一行代码就可以搞定:

  1. //合并两个有序序列
  2. while (i_start <= i_end && j_start <= j_end) {
  3.     temp[len++]=arr[i_start]<arr[j_start]?arr[i_start++]:arr[j_start++];
  4. }

**注意:**这里还有个小问题,无论是第一个序列还是第二个序列,在比较元素大小最后的时候,有可能会多出来元素。比如说上面的第二个序列的元素9,它在与元素8比较,元素8被存入到额外空间之后,那么它自然就多出来了,我们默认把它放在额外空间的最末尾处;所以我们还需要添加以下代码,写在归并方法里面;

  1. //i这个序列还有剩余元素
  2. while(i_start<=i_end){
  3.     temp[len] = arr[i_start];
  4.     len++;
  5.     i_start++;
  6. }
  7. //j这个序列还有剩余元素
  8. while(j_start<=j_end){
  9.     temp[len] = arr[j_start];
  10.     len++;
  11.     j_start++;
  12. }

2.3,第三个步骤,额外空间覆盖原始空间

这第三个步骤就比较简单了,经过前面两个步骤之后我们就把一个无序数组拆分为两个序列,再把两个序列归并成一个有序的数组,并存放在我们开辟的额外空间里面。最后一步操作就是要把额外空间里面的元素覆盖到原空间输出即可。

我们先来看代码,再来分析:

  1. //额外空间数据覆盖到原空间
  2. for(int i=0;i<temp.length;i++){
  3.     arr[start+i]=temp[i];
  4. }

循环我们很好理解,可能大家不太明白为什么arr数组里面是从start+i开始的,首先我们来分析下:

这个i只是temp里面元素的数组下标,而arr数组下标是从start开始,比如说当我们要把分好的两个序列覆盖到原空间:

额外空间与原空间的元素位置对比

额外空间与原空间的元素位置对比

所以元素覆盖要从start+i这个位置开始,而不是简单的i开始。

3,完整代码

  1. import java.util.Arrays;
  2. public class Merge_Sort {
  3.     public static void main(String[] args) {
  4.         int[] arr = {4,2,8,0,5,7,1,3,9};
  5.         System.out.println(Arrays.toString(Merge_Sort(arr, 0, arr.length - 1)));
  6.     }
  7.     /**
  8.      * @param arr   初始数组
  9.      * @param start 开始分组
  10.      * @param end   结束分组
  11.      * @return
  12.      */
  13.     public static int[] Merge_Sort(int[] arr, int start, int end) {
  14.         //当start==end时,此时分组里只有一个元素,所以这是临界点
  15.         if (start < end) {
  16.             //分成左右两个分组,再进行递归
  17.             int mid = (start + end) / 2;
  18.             //左半边分组
  19.             Merge_Sort(arr, start, mid);
  20.             //右半边分组
  21.             Merge_Sort(arr, mid + 1, end);
  22.             //递归之后再归并归并一个大组
  23.             Merge(arr, start, mid, end);
  24.         }
  25.         return arr;
  26.     }
  27.     //归并方法
  28.     public static void Merge(int[] arr, int start, int mid, int end) {
  29.         //左边分组的起点i_start,终点i_end,也就是第一个有序序列
  30.         int i_start = start;
  31.         int i_end = mid;
  32.         //右边分组的起点j_start,终点j_end,也就是第二个有序序列
  33.         int j_start = mid + 1;
  34.         int j_end = end;
  35.         //额外空间初始化,数组长度为end-start+1
  36.         int[] temp=new int[end-start+1];
  37.         int len = 0;
  38.         //合并两个有序序列
  39.         while (i_start <= i_end && j_start <= j_end) {
  40.             //当arr[i_start]<arr[j_start]值时,将较小元素放入额外空间,反之一样
  41.             if (arr[i_start] < arr[j_start]) {
  42.                 temp[len] = arr[i_start];
  43.                 len++;
  44.                 i_start++;
  45.             } else {
  46.                 temp[len] = arr[j_start];
  47.                 len++;
  48.                 j_start++;
  49.             }
  50.             //temp[len++]=arr[i_start]<arr[j_start]?arr[i_start++]:arr[j_start++];
  51.         }
  52.         //i这个序列还有剩余元素
  53.         while(i_start<=i_end){
  54.             temp[len] = arr[i_start];
  55.             len++;
  56.             i_start++;
  57.         }
  58.         //j这个序列还有剩余元素
  59.         while(j_start<=j_end){
  60.             temp[len] = arr[j_start];
  61.             len++;
  62.             j_start++;
  63.         }
  64.         //辅助空间数据覆盖到原空间
  65.         for(int i=0;i<temp.length;i++){
  66.             arr[start+i]=temp[i];
  67.         }
  68.     }
  69. }

运行结果:

结果

结果

4,算法分析

4.1,时间复杂度

归并排序把集合进行层层拆半分组。如果集合长度为n,那么拆半的层数就是logn,每一层进行归并操作的运算量是n。所以,归并排序的时间复杂度等于每一层的运算量×层级数,即O(nlogn)

4.2,空间复杂度

归并排序是需要用到额外空间的,但是每次归并所创建的额外空间都会随着方法结束而释放,因此单次归并操作开辟的最大空间是n。所以,归并排序的空间复杂度是O(n)

4.3,算法稳定性

归并排序是稳定的排序算法temp[len++]=arr[i_start]<arr[j_start]?arr[i_start++]:arr[j_start++];,这行代码可以保证当左右两部分的值相等的时候,先复制左边的值,这样可以保证值相等的时候两个元素的相对位置不变。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小丑西瓜9/article/detail/67037
推荐阅读
相关标签
  

闽ICP备14008679号