归并排序的Java实现

什么是归并排序?

归并排序(Merge Sort)是一种基于分治思想的高效排序算法。它的核心思路是将一个大问题分解为多个小问题,解决小问题后再合并结果。具体来说,就是先将数组分成两半,递归地对每一半进行排序,最后将两个有序的子数组合并成一个完整的有序数组。

归并排序的核心步骤

归并排序遵循“分而治之”的思想,分为三个步骤:
1. 分解(Divide):将待排序数组从中间分成左右两个子数组,直到每个子数组只有一个元素(此时自然有序)。
2. 解决(Conquer):递归地对左右两个子数组进行排序。
3. 合并(Combine):将两个有序的子数组合并成一个更大的有序数组。

用Java实现归并排序

下面通过一个具体的例子来理解如何用Java实现归并排序。我们使用一个简单的整数数组作为示例,逐步讲解代码逻辑。

1. 递归排序方法(mergeSort)

首先,我们需要一个递归方法来实现分解和解决步骤。方法的核心逻辑是:
- 如果数组长度小于等于1,直接返回(已经是有序的)。
- 计算数组中间位置,递归排序左右两个子数组。
- 合并两个有序子数组。

public class MergeSort {
    // 归并排序主方法
    public static void mergeSort(int[] arr) {
        // 递归终止条件:数组长度小于等于1时,无需排序
        if (arr.length <= 1) {
            return;
        }
        // 计算中间索引,将数组分成左右两部分
        int mid = arr.length / 2;
        // 递归排序左半部分
        int[] left = Arrays.copyOfRange(arr, 0, mid);
        mergeSort(left);
        // 递归排序右半部分
        int[] right = Arrays.copyOfRange(arr, mid, arr.length);
        mergeSort(right);
        // 合并左右两个有序子数组
        merge(arr, left, right);
    }
}
2. 合并方法(merge)

合并是归并排序的关键步骤,需要将两个有序子数组合并为一个有序数组。具体逻辑:
- 使用三个指针分别遍历左子数组、右子数组和合并后的结果数组。
- 比较左右子数组的元素,将较小的元素放入结果数组。
- 将剩余未遍历完的元素直接复制到结果数组。

import java.util.Arrays;

public class MergeSort {
    // ... 省略mergeSort方法 ...

    // 合并两个有序子数组到原数组arr
    private static void merge(int[] result, int[] left, int[] right) {
        int i = 0; // 左子数组指针
        int j = 0; // 右子数组指针
        int k = 0; // 结果数组指针
        // 比较左右子数组元素,将较小值放入结果数组
        while (i < left.length && j < right.length) {
            if (left[i] <= right[j]) {
                result[k] = left[i];
                i++;
            } else {
                result[k] = right[j];
                j++;
            }
            k++;
        }
        // 将左子数组剩余元素复制到结果数组
        while (i < left.length) {
            result[k] = left[i];
            i++;
            k++;
        }
        // 将右子数组剩余元素复制到结果数组
        while (j < right.length) {
            result[k] = right[j];
            j++;
            k++;
        }
    }
}
3. 完整代码与测试

将上述方法整合,编写一个完整的Java程序并测试:

import java.util.Arrays;

public class MergeSort {
    public static void main(String[] args) {
        int[] arr = {5, 2, 9, 3, 7, 6, 1, 8};
        System.out.println("排序前:" + Arrays.toString(arr));
        mergeSort(arr);
        System.out.println("排序后:" + Arrays.toString(arr));
    }

    // 归并排序主方法
    public static void mergeSort(int[] arr) {
        if (arr.length <= 1) {
            return;
        }
        int mid = arr.length / 2;
        int[] left = Arrays.copyOfRange(arr, 0, mid);
        int[] right = Arrays.copyOfRange(arr, mid, arr.length);
        mergeSort(left);
        mergeSort(right);
        merge(arr, left, right);
    }

    // 合并两个有序子数组
    private static void merge(int[] result, int[] left, int[] right) {
        int i = 0, j = 0, k = 0;
        while (i < left.length && j < right.length) {
            if (left[i] <= right[j]) {
                result[k] = left[i];
                i++;
            } else {
                result[k] = right[j];
                j++;
            }
            k++;
        }
        while (i < left.length) {
            result[k] = left[i];
            i++;
            k++;
        }
        while (j < right.length) {
            result[k] = right[j];
            j++;
            k++;
        }
    }
}

运行结果

排序前:[5, 2, 9, 3, 7, 6, 1, 8]
排序后:[1, 2, 3, 5, 6, 7, 8, 9]

算法分析

  1. 时间复杂度
    每次合并需要遍历整个数组(O(n)),递归深度为 log n(将数组分解为两半的次数),因此总时间复杂度为 O(n log n)

  2. 空间复杂度
    需要额外的临时数组(长度为 n)存储合并结果,空间复杂度为 O(n)

  3. 稳定性
    归并排序是稳定排序(相等元素的相对顺序不会改变),因为合并时若两元素相等,会优先取左子数组的元素放入结果数组。

总结

归并排序通过“分而治之”的思想实现高效排序,时间复杂度为 O(n log n),适合处理大数据量的排序场景。虽然需要额外空间,但逻辑清晰且易于理解,是初学者学习分治算法的经典案例。通过递归分解和合并有序子数组,最终得到完整的有序数组。

小夜