归并排序
- 归并排序的基本思想是将一个大的数组分解为两个或多个较小的子数组,然后对每个子数组进行排序,最后将排序好的子数组合并为一个有序的数组。
- 归并排序是一种稳定的排序算法,即相同元素的相对位置不会改变。
- 归并排序的时间复杂度是O(nlogn),其中n是数组的长度。这是因为归并排序需要进行logn层的分解和合并,每一层的操作都需要O(n)的时间。
- 归并排序的空间复杂度是O(n),因为归并排序需要额外的空间来存储合并后的数组。
- 归并排序可以采用自上而下或者自下而上的方式实现,其中自上而下的方式使用递归,自下而上的方式使用迭代。
- 归并排序适用于数据量大,并且对稳定性有要求的场景。
- 归并排序可以进行一些优化,比如在规模较小时使用插入排序,避免递归调用;在生成辅助数组时,两头小,中间大,避免多余的判断;在递归调用的每个层次交换原始数组与辅助数组的角色,节省复制元素的时间。
优缺点
优点:归并排序是一种稳定的排序算法,即相同元素的相对位置不会改变。归并排序的时间复杂度是O(nlogn),在所有的比较排序算法中效率较高。归并排序适用于数据量大,并且对稳定性有要求的场景。
缺点:归并排序的空间复杂度是O(n),因为需要额外的空间来存储合并后的数组。归并排序不是原地排序算法,即不会在原始数组上进行操作。归并排序对于小规模的数组效率不高,因为递归调用会增加开销。
代码演示
public class MergeSort {
// 主函数,供外界调用
public static void sort(int[] arr) {
int[] aux = new int[arr.length]; // 辅助数组
sort(arr, aux, 0, arr.length - 1); // 对数组arr[0..n-1]进行排序
}
// 递归函数,对数组arr[lo..hi]进行排序
private static void sort(int[] arr, int[] aux, int lo, int hi) {
if (lo >= hi) return; // 递归终止条件
int mid = lo + (hi - lo) / 2; // 中间位置
sort(arr, aux, lo, mid); // 对左半部分排序
sort(arr, aux, mid + 1, hi); // 对右半部分排序
merge(arr, aux, lo, mid, hi); // 合并两个有序子数组
}
// 合并函数,将两个有序子数组arr[lo..mid]和arr[mid+1..hi]合并为一个有序数组
private static void merge(int[] arr, int[] aux, int lo, int mid, int hi) {
// 将原数组复制到辅助数组中
for (int k = lo; k <= hi; k++) {
aux[k] = arr[k];
}
// 定义两个指针,分别指向两个子数组的起始位置
int i = lo;
int j = mid + 1;
// 从辅助数组中取出元素比较,将较小的元素放入原数组中
for (int k = lo; k <= hi; k++) {
if (i > mid) { // 左半部分用尽,取右半部分的元素
arr[k] = aux[j++];
} else if (j > hi) { // 右半部分用尽,取左半部分的元素
arr[k] = aux[i++];
} else if (aux[i] <= aux[j]) { // 左半部分的元素小于等于右半部分的元素,取左半部分的元素
arr[k] = aux[i++];
} else { // 右半部分的元素小于左半部分的元素,取右半部分的元素
arr[k] = aux[j++];
}
}
}
}
注意
- 需要申请一个和原数组一样大小的辅助数组,用来存放合并后的元素,这会增加空间复杂度为O(n)。
- 需要正确计算中间位置和子数组的边界,避免数组越界或漏掉元素。
- 需要保证合并函数是稳定的,即当两个元素相等时,先取左半部分的元素,保持相对顺序不变。
- 可以进行一些优化,比如当子数组已经有序时,不需要再进行合并;或者当子数组长度较小时,使用插入排序代替归并排序。
与各排序区别
- 归并排序和其他比较排序算法(如冒泡排序、插入排序、选择排序、快速排序)的区别是,归并排序是一种分治算法,它将一个大数组分解为两个或多个较小的子数组,然后对每个子数组进行排序,最后将排序好的子数组合并为一个有序的数组。归并排序的时间复杂度是O(nlogn),空间复杂度是O(n),是一种稳定的排序算法。
- 归并排序和快速排序的相同点是,它们都利用了分治思想,都通过递归来实现。
- 归并排序和快速排序的不同点是,归并排序先递归分解到最小粒度,然后从小粒度开始合并排序,自下而上的合并排序;而快速排序每次分解都实现整体上有序,即参照值左侧的数都小于参照值,右侧的大于参照值,自上而下的排序。