归并排序保姆式教学笔记,全程干货

      今天看了尚硅谷的数据结构和算法的归并排序课程。虽然最后理解了,但是感觉讲解的并不详细,有些需要自己领悟,所以决定写一篇保姆式教学笔记,希望能帮到大家。

归并排序主要分为三个部分理解

1.分解数组

有如下数组[8,4,5,7,1,3,6,2]

分一次: [8,4,5,7]  [1,3,6,2]

分两次:   [8,4] [5,7] [1,3] [6,2]

分三次:    [8]  [4]  [5]  [7]  [1]  [3]  [6]  [2]

第三次分解后,数组都只有一个数字,这样就把问题简单化了

分治法:先分后治

拆分代码为

public static void mergeSort(int[] arr, int left, int right) {
    //确定分界处,mid为左数组的最后一个值的索引
    int mid = (left + right) / 2;
    //当数组长度为一(一个数)时,结束递归(递归出口,不能分了)
    if (left < right) {

        //左递归
        mergeSort(arr, left, mid, temp);

        //右递归
        mergeSort(arr, mid + 1, right, temp);

     }

在理解递归时要注意:1.要把每个递归方法看作一个个独立的个体

                                     2.递归到出口后就会回溯到上一级递归方法,继续执行上一级递归的

                                      剩余代码

所以分解数组执行的顺序为

2.元素排序

子序列排序完毕后,我们就需要对分解后的子序列进行排序

每个子序列的排序是重复的过程:

这里以序列 [8,4,5,7] 和 [1, 3, 6, 2]为例:

拆分代码为

public static void merge(int[] arr, int left, int right, int mid, int[] temp) {
        //定义指针
        int l = left;//左数组的头指针

        int j = mid + 1;//右数组的头指针

        int t = 0;//中转数组赋值控制指针

        //一. 比较两数组的元素值
        //只要左数组和右数组任意一个的指针后移越界,说明比较赋值完毕
        while (l <= mid && j <= right) {
            //如果右数组的j索引值大于左数组l索引的值,就把l的值赋给中转数组的t。然后把l指针和t指针后移
            if (arr[j] > arr[l]) {
                temp[t] = arr[l];
                l++;
                t++;
            }
            //如果右数组的j索引值小于左数组l索引的值,就把j的值赋给中转数组的t。然后把j指针和t指针后移
            else {
                temp[t] = arr[j];
                j++;
                t++;
            }
        }
        //二.将指针未越界数组的剩下值填充到中转数组中,左数组和右数组均有可能
        //因为递归操作,每次左右数组都是排序完成的,所以剩下的值也是排序好的

        while (l <= mid) {
            temp[t] = arr[l];
            l++;
            t++;
        }


        while (j <= right) {
            temp[t] = arr[j];
            j++;
            t++;
        }


        //三.将中转数组copy给arr数组
        //注意:此时并非将中转数组的所有值全copy过去(提高效率),而是将每次递归操作合并后的数组copy到arr的相应位置
        int templeft = left;
        t = 0;
        while (templeft <= right) {
            arr[templeft] = temp[t];
            templeft++;
            t++;
        }

    }

 

  有很多小伙伴不理解最后为什么不直接将中转数组copy给原数组,因为直接copy会产生许多不必要的赋值操作,可以参考下图理解也可以debug分析数据。

 可以发现,前三次归并,临时数组的0元素,后三次归并临时数组的[4,5,7,8]元素都是

不需要copy的元素

3.合并数组

合并数组的操作实际上在第二步代码的最后一步操作就完成了,但是程序的执行顺序并非先分解数组再排序合并,而是随递归的回溯,逐步分解合并。

最后代码为:


public class MergeSort {
    public static void main(String[] args) {
        //原始数组
        int[] arr = {8, 4, 5, 7, 1, 3, 6, 2};
        //中转数组
        int[] temp = new int[arr.length];
        //排序
        mergeSort(arr, 0, arr.length - 1, temp);
        //打印数组
        System.out.print(Arrays.toString(arr));


    }


    //分而合方法

    public static void mergeSort(int[] arr, int left, int right, int[] temp) {
        //确定分界处,mid为左数组的最后一个值的索引
        int mid = (left + right) / 2;
        //当数组长度为一(一个数)时,结束递归(递归出口,不能分了)
        if (left < right) {

            //左递归
            mergeSort(arr, left, mid, temp);

            //右递归
            mergeSort(arr, mid + 1, right, temp);

            /*
              测试得:左递归和右递归的顺序不会影响程序结果
                     只不过是合并的顺序相反
             */

            //合并
            merge(arr, left, right, mid, temp);

        }


    }


    public static void merge(int[] arr, int left, int right, int mid, int[] temp) {
        //定义指针
        int l = left;//左数组的头指针

        int j = mid + 1;//右数组的头指针

        int t = 0;//中转数组赋值控制指针

        //一. 比较两数组的元素值
        //只要左数组和右数组任意一个的指针后移越界,说明比较赋值完毕
        while (l <= mid && j <= right) {
            //如果右数组的j索引值大于左数组l索引的值,就把l的值赋给中转数组的t。然后把l指针和t指针后移
            if (arr[j] > arr[l]) {
                temp[t] = arr[l];
                l++;
                t++;
            }
            //如果右数组的j索引值小于左数组l索引的值,就把j的值赋给中转数组的t。然后把j指针和t指针后移
            else {
                temp[t] = arr[j];
                j++;
                t++;
            }
        }
        //二.将指针未越界数组的剩下值填充到中转数组中,左数组和右数组均有可能
        //因为递归操作,每次左右数组都是排序完成的,所以剩下的值也是排序好的

        while (l <= mid) {
            temp[t] = arr[l];
            l++;
            t++;
        }


        while (j <= right) {
            temp[t] = arr[j];
            j++;
            t++;
        }


        //三.将中转数组copy给arr数组
        //注意:此时并非将中转数组的所有值全copy过去(提高效率),而是将每次递归操作合并后的数组copy到arr的相应位置
        int templeft = left;
        t = 0;
        while (templeft <= right) {
            arr[templeft] = temp[t];
            templeft++;
            t++;
        }

    }


}

可以发现:长度为n的数组归并的次数为n-1次,整个排序的时间复杂度为O(nlogn),归并排序采取空间换时间的方式,排序速度要比冒泡,交换,选择,插入快。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值