【算法Algorithm】归并(Merge)排序

本文详细介绍了归并排序的算法思想,通过不断二分和归并操作,直至将无序数组排序。提供的Java代码展示了如何递归地进行二分和归并过程,最终实现整个数组的排序。在每一步归并过程中,都展示了临时数组的构建和原数组的更新,清晰揭示了算法的工作原理。归并排序的时间复杂度为O(n log n),空间复杂度为O(n),是一种稳定的排序算法。
摘要由CSDN通过智能技术生成

算法思想

把待排序数组进行二分,左边归并排序,右边归并排序;然后再两者归并
如果左(或右)边还是无序的,那么对左(或右)再进行二分,分成更小的左和右;左边归并,右边归并,然后再两者归并
...
依次类推

那程序是怎么知道分到什么时候“正好”左边有序且右边也有序呢?

答案是不知道(人可以看一眼待排数组并作出像“前三个有序,后三个有序”的判断),所以是分到最小,即紧邻的“两个”。所以站在程序的角度就是

对待排序数组进行二分,再二分......直到分到最小;
然后再从最小的一对左和右进行归并,成为一个较大的左(或右);较大的一对左和右再进行归并,成为更大的左(或右)
...
直到最后两个最大的左和最大的右进行归并,生成排好序的整个数组

代码实现

public static void main(String[] args) { 
    int[] arr = {2, 5, 6, 8, 9, 7, 4, 1, 3, 0, 11, 15, 13, 18, 14, 16, 19, 17, 12, 10, 20};
    // 要归并排序的数组是arr,要排序整个数组
    sort(arr, 0, arr.length - 1);
    System.out.println("最终结果:" + Arrays.toString(arr));
}

/**
* 递归排序
* @param arr   要排序的数组
* @param left  左半部分的起始索引位置
* @param right 最右(右边界)的索引位置
*/
private static void sort(int[] arr, int left, int right) {
    // 递归的终止条件
    if (left == right) {
        return;
    }
    
    // 中间索引位置(有人说,(left + right) / 2 这种算法不好)
    int mid = left + (right -left) /2;
    // 左半部分排序(包含中间索引对应的值)
    sort(arr, left, mid);
    // 右半部分排序
    sort(arr, mid + 1, right);
    // 归并
    merge(arr, left, right, mid);
}

/**
* 归并操作
* @param arr   要排序的数组
* @param left  左半部分的起始索引位置
* @param right 最右(右边界)的索引位置
* @param mid   二分的中间索引位置   
*/
private static void merge(int[] arr, int left, int right, int mid) {
    // 临时数组
    int[] tmp = new int[right - left + 1];
    // 左半部分的开始位置,该指针指向这个位置
    int i = left;
    // 右半部分的开始位置,该指针指向这个位置
    int j = mid + 1;
    // 临时数组的起始位置,该指针指向这个位置
    int k = 0;

    if (left > j) {
        throw new RuntimeException("左指针索引大于有指针索引");
    }
    if (left == j) {
        return;
    }    
    
    // 左边且右边指针未到头,则把数据放到临时数组    
    while (i <= mid && j <= right) {
        // 归并,依次把左右两半中较小的放入临时数组
        tmp[k++] = (arr[i] <= arr[j]) ? arr[i++] : arr[j++];
    }
    
    // 右边指针到头,如果左边还有,则左半部分剩余放到临时数组
    while (i <= mid) {
        tmp[k++] = arr[i++];
    }
    // 左边指针到头,如果右半部分还有,则右半部分剩余放到临时数组
    while (j <= right) {
        tmp[k++] = arr[j++];
    }

    /* 把临时数组的数据(已排好序)按索引对应的位置“重置”原数组;
       注意,对应的位置不一定是0对应0,1对应1
    */
    for (int index = 0; index < tmp.length; index ++) {
        arr[left++] = tmp[index];
    }
    
    // 打印每次归并临时数组的数据
    System.out.println(Arrays.toString(tmp));
    // 打印每次归并重置后的原数组
    System.out.println(Arrays.toString(arr));
}

程序运行结果

tmp:[2, 5]
arr:[2, 5, 6, 8, 9, 7, 4, 1, 3, 0, 11, 15, 13, 18, 14, 16, 19, 17, 12, 10, 20]
tmp:[2, 5, 6]
arr:[2, 5, 6, 8, 9, 7, 4, 1, 3, 0, 11, 15, 13, 18, 14, 16, 19, 17, 12, 10, 20]
tmp:[8, 9]
arr:[2, 5, 6, 8, 9, 7, 4, 1, 3, 0, 11, 15, 13, 18, 14, 16, 19, 17, 12, 10, 20]
tmp:[7, 8, 9]
arr:[2, 5, 6, 7, 8, 9, 4, 1, 3, 0, 11, 15, 13, 18, 14, 16, 19, 17, 12, 10, 20]
tmp:[2, 5, 6, 7, 8, 9]
arr:[2, 5, 6, 7, 8, 9, 4, 1, 3, 0, 11, 15, 13, 18, 14, 16, 19, 17, 12, 10, 20]
tmp:[1, 4]
arr:[2, 5, 6, 7, 8, 9, 1, 4, 3, 0, 11, 15, 13, 18, 14, 16, 19, 17, 12, 10, 20]
tmp:[1, 3, 4]
arr:[2, 5, 6, 7, 8, 9, 1, 3, 4, 0, 11, 15, 13, 18, 14, 16, 19, 17, 12, 10, 20]
tmp:[0, 11]
arr:[2, 5, 6, 7, 8, 9, 1, 3, 4, 0, 11, 15, 13, 18, 14, 16, 19, 17, 12, 10, 20]
tmp:[0, 1, 3, 4, 11]
arr:[2, 5, 6, 7, 8, 9, 0, 1, 3, 4, 11, 15, 13, 18, 14, 16, 19, 17, 12, 10, 20]
tmp:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11]
arr:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 15, 13, 18, 14, 16, 19, 17, 12, 10, 20]
tmp:[13, 15]
arr:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 13, 15, 18, 14, 16, 19, 17, 12, 10, 20]
tmp:[13, 15, 18]
arr:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 13, 15, 18, 14, 16, 19, 17, 12, 10, 20]
tmp:[14, 16]
arr:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 13, 15, 18, 14, 16, 19, 17, 12, 10, 20]
tmp:[13, 14, 15, 16, 18]
arr:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 13, 14, 15, 16, 18, 19, 17, 12, 10, 20]
tmp:[17, 19]
arr:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 13, 14, 15, 16, 18, 17, 19, 12, 10, 20]
tmp:[12, 17, 19]
arr:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 13, 14, 15, 16, 18, 12, 17, 19, 10, 20]
tmp:[10, 20]
arr:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 13, 14, 15, 16, 18, 12, 17, 19, 10, 20]
tmp:[10, 12, 17, 19, 20]
arr:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 13, 14, 15, 16, 18, 10, 12, 17, 19, 20]
tmp:[10, 12, 13, 14, 15, 16, 17, 18, 19, 20]
arr:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 10, 12, 13, 14, 15, 16, 17, 18, 19, 20]
tmp:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
arr:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
最后结果:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]

复杂度和稳定性

时间复杂度

  • 最坏时间复杂度:O(nlogn)
  • 最好时间复杂度:O(nlogn)
  • 平均时间复杂度:O(nlogn)

空间复杂度:O(n)

稳定性:稳定

参考文章

https://www.runoob.com/w3cnote/merge-sort.html

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值