归并排序(Merge Sort)是一种经典的分治算法,其基本思路可用一句话概括:将数组划分为两部分,分别排序后合并为一个有序数组。听起来简单,但背后的逻辑却精妙无比。归并排序通过递归的方式,将问题逐步拆解为最小单位,再通过合并操作将结果逐步整合。
从递归到合并:一步步拆解归并排序
递归的本质
递归看似复杂,其实就是通过不断压栈,层层拆解问题,再逐步回溯解决。归并排序正是利用递归的方式,将一个大问题化简为多个小问题。例如,当我们面对一个数组 [3, 5, 6, 0, 1, 2] 时,归并排序会这样运作:
- 拆分阶段:将数组拆分为两个子数组:左侧
[3, 5, 6]和右侧[0, 1, 2]。然后对两个子数组分别进行递归排序。 - 排序合并:将左侧和右侧排序后的结果合并为一个新的有序数组。
合并的核心
在合并阶段,我们会用到一个辅助数组以及两个指针,分别指向左右两个子数组的起始位置:
- 比较两个指针所指向的元素,将较小的值放入辅助数组中,并移动相应指针。
- 如果某一子数组的指针已经到达末尾,直接将另一个子数组剩余的元素拷贝到辅助数组。
- 最后将辅助数组的内容复制回原数组。
以 [3, 5, 6] 和 [0, 1, 2] 为例,合并过程如下:
- 比较
3和0,将0放入辅助数组。 - 比较
3和1,将1放入辅助数组。 - 重复上述操作,直到所有元素合并完毕。
最终结果是一个完全有序的数组 [0, 1, 2, 3, 5, 6]。
时间复杂度分析:为何是 O(n log n)?
归并排序的时间复杂度被证明为 O(n log n),这源于其两大特点:
- 分解层数:每次将数组分为两半,分解过程的层数为
log n(n 是数组长度)。 - 合并复杂度:每层的合并操作需要遍历整个数组,因此为 O(n)。
无论数组初始顺序如何(完全无序或接近有序),归并排序的时间复杂度始终是 O(n log n),这使其成为处理大规模数据的理想选择。
代码实现:一步步拆解归并排序
以下是归并排序在 Java 中的实现:
public class MergeSort {
// 主排序函数
public void mergeSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
mergeSortRecursive(arr, 0, arr.length - 1);
}
// 递归排序函数
private void mergeSortRecursive(int[] arr, int left, int right) {
if (left >= right) {
return;
}
int mid = left + (right - left) / 2; // 计算中点
mergeSortRecursive(arr, left, mid); // 排序左半部分
mergeSortRecursive(arr, mid + 1, right); // 排序右半部分
merge(arr, left, mid, right); // 合并两部分
}
// 合并函数
private void merge(int[] arr, int left, int mid, int right) {
int[] help = new int[right - left + 1]; // 辅助数组
int i = 0, p1 = left, p2 = mid + 1;
// 合并过程:将较小的元素放入辅助数组
while (p1 <= mid && p2 <= right) {
help[i++] = arr[p1] <= arr[p2] ? arr[p1++] : arr[p2++];
}
// 将剩余部分加入辅助数组
while (p1 <= mid) help[i++] = arr[p1++];
while (p2 <= right) help[i++] = arr[p2++];
// 将排序结果复制回原数组
for (i = 0; i < help.length; i++) {
arr[left + i] = help[i];
}
}
public static void main(String[] args) {
MergeSort sorter = new MergeSort();
int[] arr = {3, 5, 6, 0, 1, 2};
sorter.mergeSort(arr);
System.out.println("Sorted Array: " + Arrays.toString(arr));
}
}
归并排序的优劣势
优点
- 稳定性:归并排序保留了相同元素的相对顺序。
- 高效性:无论数组多么无序,其时间复杂度始终为 O(n log n)。
- 适用性强:适合排序大规模数组。
缺点
- 空间复杂度较高:需要额外的辅助数组,空间复杂度为 O(n)。
- 不适合小数组:小规模数据时,递归带来的开销可能不如直接使用插入排序。
结语
归并排序不仅是一个高效的排序算法,更是递归思想的绝佳范例。通过将问题分解到足够小的规模,递归的力量得以完美展现。希望这篇文章能帮助你更好地理解归并排序的原理与实现!