白话解析归并排序

    简单来说,归并排序主要是将多个有序的序列合并为一个有序的序列.

    我们首先看下2个简单的数 5 , 2  ,那么直接将5和2比较交换位置就变成 2,5 。然后再加2个数 4,3 ,同理,比较交换变成 3,4 。 

    现在我们将2,5 和 3,4 这2个序列合并为一个有序序列, 对于没有什么算法基础的同学第一个想到可能是先找出最小的数,然后是第二小的数,第三小的数等等,最后依次将其放入一个新数组中即可完成。

    但是今天我们来讨论另一种方案 , 我们先将2个序列的第一个数2和3进行比较选出较小的2放进新数组,然后将第一个序列下标后移一位为5继续和3比较选出较小的3放进新数组,再将第二个序列下标后移一位为4继续和5比较选出较小的4放进新数组,最后将5放进新数组,这样新数组的数据就为 2,3,4,5 的有序序列。这就叫做 归并排序。

现在我们来看下归并排序的代码:

public static void mergeSort(int arr[],int left,int right){
        if(left>=right)return;

        int mid=(right+left)/2;

        mergeSort(arr,left,mid);
        mergeSort(arr,mid+1,right);

        merge(arr,left,mid,right);

    }

    private static void merge(int[] arr, int left, int mid, int right) {

        int tmp[]=new int[right-left+1];
        int leftIndex=left;
        int rightIndex=mid+1;
        int tmpIndex=0;

        while (leftIndex<=mid && rightIndex<=right){
            if(arr[leftIndex]<=arr[rightIndex]){
                tmp[tmpIndex++]=arr[leftIndex++];
            }else{
                tmp[tmpIndex++]=arr[rightIndex++];
            }
        }

        int start=leftIndex,end=mid;

        if(rightIndex<=right){
            start=rightIndex;
            end=right;
        }

        while (start<=end){
            tmp[tmpIndex++]=arr[start++];
        }

        for(leftIndex=0;leftIndex<=right-left;leftIndex++){
            arr[left+leftIndex]=tmp[leftIndex];
        }
    }

    mergeSort 方法中的 arr 为排序的数组, left 和 right 分别为arr数组 的左边界下标和右边界下标,这里是采用分治的方式对数组递归,然后进行归并排序的操作。也就是对数组不断进行二分,直至左边界大于等于右边界不能二分为止。

  现在我们来看下数组 5, 3, 2, 6, 4, 1 的归并执行过程,其中合并就是指的是执行merge方法:

                                                 5 3 2 6 4 1                 left=0   right=5
                    第一次递归                5 3 2                     left=0   right=2
                    第二次递归                  5 3                      left=0   right=1
                    第三次递归                   5                        left=0   right=0  left>=right,达到终止条件返回
                    第四次递归                   3                        left=1   right=1  left>=right,达到终止条件返回
                    第一次合并           3 5 2 6 4 1                 left=0  mid=0 right=1
                    第五次递归                   2                        left=2   right=2  left>=right,达到终止条件返回
                    第二次合并           2 3 5 6 4 1                 left=0  mid=1 right=2
                    第六次递归                6 4 1                     left=3   right=5
                    第六次递归                 6 4                       left=3   right=4
                    第六次递归                  6                         left=3   right=3  left>=right,达到终止条件返回
                    第七次递归                  4                         left=4   right=4  left>=right,达到终止条件返回
                    第三次合并           2 3 5 4 6 1                 left=3  mid=3 right=4
                    第八次递归                  1                         left=5   right=5  left>=right,达到终止条件返回
                    第四次合并           2 3 5 1 4 6                 left=3  mid=4 right=5
                    第五次合并           1 2 3 4 5 6                 left=0  mid=2 right=5

 

    上面就是执行归并排序的详细过程了,标红是归并过程中发生的数据交换。

    现在我们来详细解析一下merge方法,以上面的最后一次归并为例,我们看下merge的执行过程:

     首先会创建一个 tmp 数组,用于存放已经排序的数据。然后以 mid 为中心分为左右2个子数组,分别用 leftIndexrightIndex 2个下标进行遍历,选出较小的那个数,将其对应的下标加1,循环往复,直至遍历到终点为止。最后将子数组中剩余的数据放进tmp数组中,将tmp中的数据复制进原数组 arr 中即可。

 

归并优化:

    针对merge方法,还有一种优化的方式:

private static void mergeGuard(int[] arr, int left, int mid, int right) {
        int[] leftArr = new int[mid - left + 2];
        int[] rightArr = new int[right - mid + 1];

        for (int i = 0; i <= mid - left; i++) {
            leftArr[i] = arr[left + i];
        }
        // 第一个数组添加哨兵(最大值)
        leftArr[mid - left + 1] = Integer.MAX_VALUE;

        for (int i = 0; i < right - mid; i++) {
            rightArr[i] = arr[mid + 1 + i];
        }
        // 第二个数组添加哨兵(最大值)
        rightArr[right-mid] = Integer.MAX_VALUE;

        int i = 0;
        int j = 0;
        int k = left;
        while (k <= right) {
            // 当左边数组到达哨兵值时,i不再增加,直到右边数组读取完剩余值,同理右边数组也一样
            if (leftArr[i] <= rightArr[j]) {
                arr[k++] = leftArr[i++];
            } else {
                arr[k++] = rightArr[j++];
            }
        }
    }

   这个主要是将左右数组的边界分别存储整型的最大值,这样在归并的时候,就可以将左右数组中的所有数据都放进arr中,不需要在数据比较完成后,额外将边界数据放入新数组中。

 

总结:

    归并排序,本质上就是采用分治的方式,将序列一点点拆分为一个个数,最后进行合并。但是人的思维都是正向的,将一个个数合并为一个有序序列时很容易理解,逆向将序列拆分再合并就比较难理解一点。本文先采用拆分的方式,带大家理解归并的核心,然后带领大家一步步的去深入理解归并排序。

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值