归并排序(Merge Sort)算法
归并排序是一种高效、稳定的排序算法,广泛应用于计算机科学中。它基于分治法(Divide and Conquer)的原理。以下是归并排序的主要步骤和特点:
基本步骤
分割(Divide):
将原始数组分成若干子数组,最理想的情况是将它们分成只有一个元素的子数组。通常这是通过递归实现的,每次递归将数组分为两半,直到每个子数组只包含一个元素或没有元素。
合并(Conquer):
将分割后的子数组重新合并成一个排序好的数组。合并过程是归并排序的核心,它将两个已排序的子数组合并成一个。
在合并过程中,我们通常需要一个临时数组来存储合并后的元素,以确保在合并时不会覆盖原数组中尚未处理的元素。
详细过程
假设有一个数组 A 需要排序:
递归地将数组分成两半:
1.如果数组有多于一个元素,则将它分为两个子数组。
2.对每个子数组递归地执行相同的分割过程,直到每个子数组只有一个元素或为空。
合并排序后的子数组:
1.在合并的过程中,我们按顺序比较两个子数组的元素,将较小的元素先放入临时数组中。
2.如果一个子数组的所有元素都已经被移动到临时数组中,则将另一个子数组的剩余部分复制过来。
3.最后,将临时数组中排序好的元素复制回原数组相应的位置。
特点和复杂度
时间复杂度:
归并排序在最好、最坏和平均情况下都有 O(n log n) 的时间复杂度,其中 n 是数组中的元素数量。这使得归并排序非常高效,特别是对于大数据集。
空间复杂度:
归并排序不是原地排序算法,因为它需要与原数组相同大小的额外空间来存储临时数组,所以其空间复杂度为 O(n)。
稳定性:
归并排序是稳定的排序算法,这意味着相同的元素在排序后会保持它们原始的顺序。
应用场景:
归并排序特别适用于不适合在内存中一次性处理的大数据集,如外部排序。它也是许多复杂排序算法的基础,例如 Timsort。
public class Solution {
/**
* 归并排序的主方法
* @param A 待排序的整数数组
*/
public void sortIntegers(int[] A) {
if (A == null || A.length == 0) {
return; // 检查数组是否为空或无元素
}
int[] temp = new int[A.length]; // 创建一个临时数组用于合并
mergeSort(A, 0, A.length - 1, temp); // 调用归并排序的递归函数
}
/**
* 递归地对数组进行归并排序
* @param A 原始数组
* @param start 开始索引
* @param end 结束索引
* @param temp 临时存储数组
*/
private void mergeSort(int[] A, int start, int end, int[] temp) {
if (start >= end) {
return; // 递归结束条件
}
int middle = (start + end) / 2; // 找到中间索引
mergeSort(A, start, middle, temp); // 递归排序左半部分
mergeSort(A, middle + 1, end, temp); // 递归排序右半部分
merge(A, start, end, temp); // 合并两个已排序的部分
}
/**
* 将两个已排序的子数组合并成一个排序数组
* @param A 原始数组
* @param start 开始索引
* @param end 结束索引
* @param temp 临时存储数组
*/
private void merge(int[] A, int start, int end, int[] temp) {
int middle = (start + end) / 2;
int leftIndex = start; // 左子数组的开始索引
int rightIndex = middle + 1; // 右子数组的开始索引
int index = start; // 临时数组的索引
// 合并两个子数组
while (leftIndex <= middle && rightIndex <= end) {
if (A[leftIndex] < A[rightIndex]) {
temp[index++] = A[leftIndex++];
} else {
temp[index++] = A[rightIndex++];
}
}
// 将剩余的左子数组元素复制到临时数组
while (leftIndex <= middle) {
temp[index++] = A[leftIndex++];
}
// 将剩余的右子数组元素复制到临时数组
while (rightIndex <= end) {
temp[index++] = A[rightIndex++];
}
// 将排序后的临时数组复制回原数组
for (int i = start; i <= end; i++) {
A[i] = temp[i];
}
}
}
代码讲解
sortIntegers 方法
这是归并排序的主方法,用于对外提供排序功能。它接受一个整数数组 A 作为参数。
参数检查:首先检查数组 A 是否为空或者长度为零。如果是,就直接返回,因为没有需要排序的元素。
创建临时数组: 创建一个与 A 同等长度的临时数组 temp。这个临时数组用于在合并过程中存储合并后的元素。
调用递归排序方法: 调用 mergeSort 方法,传入数组 A、起始索引 0、结束索引 A.length - 1 和临时数组 temp,以开始排序过程。
mergeSort 方法
这是归并排序的递归方法,用于实际执行排序。
递归结束条件: 如果 start 索引大于或等于 end 索引,说明当前处理的子数组不能再分割,方法就会返回。
找到中间索引: 通过 (start + end) / 2 计算出当前子数组的中间索引 middle。
递归排序左半部分: 对数组 A 中从 start 到 middle 的部分进行递归排序。
递归排序右半部分: 对数组 A 中从 middle + 1 到 end 的部分进行递归排序。
合并两个已排序的部分: 调用 merge 方法合并两个已排序的子数组。
merge 方法
这个方法用于合并两个已排序的子数组。
初始化指针: leftIndex 指向左子数组的开始位置,rightIndex 指向右子数组的开始位置,index 用于跟踪临时数组 temp 中的当前位置。
合并过程:当 leftIndex 和 rightIndex 都没有超过各自子数组的界限时,比较两个子数组当前元素的大小,将较小的元素复制到 temp 数组中,并移动相应的索引。如果一个子数组的元素都已经被复制到 temp 中,而另一个子数组还有剩余元素,就将这些剩余元素直接复制到 temp 中。
复制回原数组: 将临时数组 temp 中的元素复制回原数组 A 的相应位置,完成合并。
总结
归并排序通过递归地将数组分成越来越小的子数组,直到每个子数组只包含一个元素或为空,然后再将这些有序的子数组合并成一个完整的有序数组。这种“分而治之”的策略使得归并排序非常有效,尤其是在处理大型数组时。由于需要额外的临时数组来存储合并后的元素,所以归并排序的空间复杂度为 O(n)。