1. 简介

本篇文章将带你深入理解 归并排序(Merge Sort)算法及其在 Java 中的实现方式

归并排序是一种非常高效的排序方法,它基于 "分治思想(Divide and Conquer)" 实现。如果你追求稳定性和效率,尤其是在处理大规模数据时,归并排序是一个值得掌握的经典算法。

2. 算法原理

归并排序是一种典型的“分治”算法,它的核心思想是:

  • 将原问题不断拆分成更小的子问题,直到每个子问题足够简单可以直接解决;
  • 然后自底向上地合并这些子问题的解,最终得到完整问题的答案。

由于其天然的递归特性,归并排序非常适合用递归来实现。

整个过程可以分为两个阶段:

  1. 划分(Divide)阶段
    • 把数组从中间分成两部分;
    • 对每一半继续递归划分,直到数组长度为 1。
  2. 合并(Conquer)阶段
    • 自底向上地将两个已排序的子数组合并成一个有序数组;
    • 最终完成整个数组的排序。

来看一个例子:数组 {10, 6, 8, 5, 7, 3, 4} 的完整归并排序流程如下图所示:

mergesort1

从图中可以看到,数组被不断二分,直到只剩单个元素;然后开始合并,并在合并过程中进行排序,最终得到一个完全有序的数组。

3. Java 实现

我们来动手写一个归并排序的 Java 实现版本。

3.1 主函数 mergeSort

public static void mergeSort(int[] a, int n) {
    if (n < 2) {
        return;
    }
    int mid = n / 2;
    int[] l = new int[mid];
    int[] r = new int[n - mid];

    for (int i = 0; i < mid; i++) {
        l[i] = a[i];
    }
    for (int i = mid; i < n; i++) {
        r[i - mid] = a[i];
    }
    mergeSort(l, mid);
    mergeSort(r, n - mid);

    merge(a, l, r, mid, n - mid);
}

这个函数的作用是:

  • 如果数组长度小于 2,说明已经不能再拆分了,直接返回;
  • 否则计算中点,创建左右两个临时数组;
  • 分别复制原数组的前半段和后半段到这两个临时数组;
  • 递归调用自身分别对左右两部分进行排序;
  • 调用 merge() 方法将两个已排序的子数组合并回原数组。

3.2 合并函数 merge

public static void merge(int[] a, int[] l, int[] r, int left, int right) {
    int i = 0, j = 0, k = 0;

    // 比较左右数组中的元素,依次放入原数组
    while (i < left && j < right) {
        if (l[i] <= r[j]) {
            a[k++] = l[i++];
        } else {
            a[k++] = r[j++];
        }
    }

    // 处理左数组剩余元素
    while (i < left) {
        a[k++] = l[i++];
    }

    // 处理右数组剩余元素
    while (j < right) {
        a[k++] = r[j++];
    }
}

这段代码逻辑清晰:

  • 使用三个指针分别指向左子数组、右子数组和原数组;
  • 依次比较左右数组当前元素,把较小的放到原数组;
  • 当其中一个数组遍历完之后,将另一个数组剩下的元素直接追加到原数组末尾。

3.3 单元测试示例

@Test
public void positiveTest() {
    int[] actual = { 5, 1, 6, 2, 3, 4 };
    int[] expected = { 1, 2, 3, 4, 5, 6 };
    MergeSort.mergeSort(actual, actual.length);
    assertArrayEquals(expected, actual);
}

通过这个测试用例可以验证我们的归并排序是否正确运行。

4. 时间与空间复杂度分析

归并排序的时间复杂度可以通过递推关系表达为:

T(n) = 2T(n/2) + O(n)

其中:

  • 2T(n/2) 表示对左右两个子数组分别进行排序所需时间;
  • O(n) 是合并两个子数组所需的线性时间。

解此递推式可得总时间复杂度为:

O(n log n)

⚠️ 这个复杂度无论是在最好、最坏还是平均情况下都成立 —— 因为归并排序总是将数组平分成两部分,不会有极端情况。

空间复杂度方面:

❌ 归并排序并不是原地排序算法,每次递归都需要开辟新的临时数组用于存储子数组,因此空间复杂度为:

O(n)

5. 总结

在这篇文章中,我们详细讲解了归并排序的基本原理以及如何在 Java 中具体实现。它是一种稳定的、时间复杂度恒定的排序算法,在很多场景下都非常实用。

虽然空间开销略高,但在需要稳定排序或外部排序(如磁盘文件排序)时,它仍然是首选之一。

完整源码可以在 GitHub 上找到:https://github.com/eugenp/tutorials/tree/master/algorithms-modules/algorithms-sorting


原始标题:Merge Sort in Java | Baeldung