数据结构与算法——归并排序

感谢阅读!❤️
如果这篇文章对你有帮助,欢迎 **点赞** 👍 和 **关注** ⭐,获取更多实用技巧和干货内容!你的支持是我持续创作的动力!
**关注我,不错过每一篇精彩内容!**


一、归并排序的算法思路

归并排序(Merge Sort)是一种基于分治策略(Divide and Conquer)的排序算法,其核心思想是将数组不断分割为更小的子数组,直到子数组长度为1(天然有序),然后通过归并操作将有序的子数组合并为一个完整的有序数组。

二、算法步骤

  1. 划分阶段(Divide)

    • 递归分割:将待排序的数组从中间位置 mid = (left + right) / 2 分为左右两个子数组。
    • 递归终止条件:当子数组长度为1时,递归终止,因为单个元素默认有序。
    • 递归排序:分别对左右子数组递归执行归并排序
  2. 合并阶段(Conquer)

    • 归并操作:将两个已排序的子数组合并为一个有序数组。
    • 申请临时空间:创建一个大小为 right - left + 1 的临时数组 temp
    • 双指针比较:使用两个指针分别指向左右子数组的起始位置,逐个比较元素,将较小的元素放入 temp 中,并移动指针。
    • 剩余元素处理:当某一子数组的所有元素被遍历完后,将另一子数组的剩余元素直接复制到 temp 中。
    • 覆盖原数组:将 temp 中的有序元素复制回原数组的对应位置。

三、示例数组排序过程

数组:[5, 8, 5, 2, 9] 的排序过程

  • 第一步,将数组从中间分开
    [5, 8, 5, 2, 9][5, 8, 5][2, 9]
  • 第二步:继续拆分左右子数组
    [5, 8, 5][5, 8][5]
    [5, 8][5][8]
    [2, 9][2][9]
  • 第三步:合并单个元素的数组
  • 合并 [5][8][5] + [8] → [5, 8]
  • 合并 [5, 8][5][5, 8] + [5] → [5, 5, 8]
  • 合并 [2][9][2] + [9] → [2, 9]
  • 合并 [5, 5, 8][2, 9][5, 5, 8] + [2, 9] → [2, 5, 5, 8, 9]

在这里插入图片描述

四、代码实现

import java.util.Arrays;

public class MergeSort {
    public static void main(String[] args) {
        //测试代码
        int[] arr = {5, 8, 5, 2, 9};
        mergeSort(arr);
        System.out.println(Arrays.toString(arr));
    }

    public static void mergeSort(int[] array) {
        int[] temp = new int[array.length];  // 辅助数组
        mergeSort(array, 0, array.length - 1, temp);
    }

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

        //求中点值
        int mid = (left + right) >>> 1;  // 防止整数溢出

        // 递归排序左半部分
        mergeSort(arr, left, mid, temp);

        // 递归排序右半部分
        mergeSort(arr, mid + 1, right, temp);

        // 合并两个有序子数组
        merge(arr, left, mid, right, temp);
    }

    private static void merge(int[] arr, int left, int mid, int right, int[] temp) {
        // 将原数组的 [left...right] 复制到辅助数组中
        System.arraycopy(arr, left, temp, left, right - left + 1);

        int i = left;   // 指向左半部分的起始位置
        int j = mid + 1; // 指向右半部分的起始位置
        int k = left;   // 指向原数组中待填充的位置

        // 合并两个有序数组
        while (i <= mid && j <= right) {
            if (temp[i] <= temp[j]) {
                arr[k++] = temp[i++];
            } else {
                arr[k++] = temp[j++];
            }
        }

        // 处理剩余元素
        while (i <= mid) {
            arr[k++] = temp[i++];
        }

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

五、时间复杂度分析

时间复杂度:最坏/平均/最好:O(n log n)

  1. 递归深度
    每次递归将数组划分为两半,递归深度为 log n 层(n 为数组长度)。

  2. 每层操作时间
    合并操作:每层递归中,所有子数组的合并总时间复杂度为 O(n)。例如,若数组长度为 n,每层合并需要遍历所有 n 个元素。

  3. 总时间复杂度
    最坏/平均/最好情况:归并排序的时间复杂度始终为 O(n log n)

  • 数学推导:根据主定理(Master Theorem),归并排序的递归公式为:
    T ( n ) = 2 T ( n 2 ) + O ( n ) T(n) = 2T\left(\frac{n}{2}\right) + O(n) T(n)=2T(2n)+O(n)
    其中, 2 T ( n 2 ) 2T\left(\frac{n}{2}\right) 2T(2n) 表示递归处理左右子数组, O ( n ) O(n) O(n) 表示合并操作。根据主定理的第二种情况,解得:
    T ( n ) = O ( n log ⁡ n ) T(n) = O(n \log n) T(n)=O(nlogn)

六、空间复杂度分析

空间复杂度:O(n)

  1. 合并操作的空间
    临时数组:归并操作需要额外的 O(n) 空间存储合并结果。例如,合并两个子数组时,需创建一个大小为 n 的临时数组 temp
  2. 递归栈空间
    递归调用栈:由于归并排序是递归实现的,递归栈的最大深度为 O(log n)。每一层递归的空间复杂度为 O(1),因此递归栈总空间复杂度为 O(log n)
  3. 总空间复杂度
    整体空间复杂度:归并排序的空间复杂度为 O(n),由合并操作的临时数组主导。递归栈空间 O(log n) 相对较小,通常忽略不计。

七、稳定性分析

稳定性:稳定排序

  1. 分治过程中的稳定性
    在归并排序的分治过程中,数组被递归地划分为更小的子数组,直到每个子数组仅包含一个元素。由于单个元素天然有序且无需进一步操作,因此这个阶段不会影响元素的相对顺序。

  2. 合并过程中的稳定性
    归并排序的关键在于合并两个已排序的子数组时如何处理相同元素。在合并操作中,当遇到左右两个子数组中的元素相同时,如果总是优先选择左子数组中的元素,则可以确保这些相同元素在排序前后的相对顺序不变。

八、适用场景

  • 大规模数据排序
  • 链表排序
  • 稳定性要求高的场景

九、总结

特性描述
时间复杂度最坏/平均/最好:O(n log n)
空间复杂度O(n)(需要额外存储空间)
稳定性稳定
适用场景大规模数据排序、链表排序 、 稳定性要求高的场景
优点稳定、高效、适用于多种数据结构、无最坏输入影响
缺点需要额外空间、实现略复杂、对于小数组效率不如插入排序等简单排序
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值