归并排序
概述与思路
所谓归并,是指将两个或多个有序序列合并成一个新的有序序列。
默认的归并排序是指 2路归并排序
:
- 即若待排序列有
n
个元素,将其视为n
个长度为 1 的有序序列,两两归并后得到⌈n/2⌉
个有序序列 - 继续两两归并,最终得到长度为
n
的有序序列
例如 ⏯
该图源于:
十大经典排序算法(动图演示) - 一像素 - 博客园 (cnblogs.com)
采用 递归 方法,整体思路为 分而治之:
- 由上而下,将待排序列递归地划分为左右两个子序列,直至子序列不可再划分为止(即每个子序列长度为 1 )
- 由下而上,两两归并,最终合并成一个有序序列
代码CPP实现
递归划分,整体结构:
// 传入的 temp[] 是在主函数 main 内开辟的辅助数组,可以在递归栈中重复使用
void mergeSort(int arr[], int temp[],int low, int high) {
if (low < high) {
int mid = low + ((high - low) >> 1); // 从数组中间位置划分,深度优先划分左侧,后右侧
mergeSort(arr, temp, low, mid);
mergeSort(arr, temp, mid + 1, high);
merge(arr, temp, low, mid, high); // 两两归并操作
}
}
❗️ 注意:
-
int mid = low + ((high - low) >> 1); int mid = (low + high) / 2;
当数组长度很大时,第一句可以避免溢出
-
传入
temp[]
是为了 在merge()
归并时使用,避免了在merge()
递归栈内反复开辟和删除辅助空间,后续代码注解⭕️
归并操作
void merge(int arr[],int temp[], int low, int mid, int high) {
for (int k = low; k <= high; k++)
temp[k] = arr[k]; // 辅助数组temp[]赋值,用于比较,arr[]内交换
int i = low, j = mid + 1, k = low; // i为左侧子序列下标,j为右侧子序列下标,k为arr[]排好序的位置
while (i <= mid && j <= high) { // 两个子序列元素“交替” 比较,一直到某序列比较完毕
if (temp[i] <= temp[j]) // 这个<= 决定了归并排序的稳定性
arr[k++] = temp[i++]; // 先赋值,然后后移一位
else
arr[k++] = temp[j++];
}
while (i <= mid) // 以下操作是剩余某有序子序列,追加到尾部
arr[k++] = temp[i++];
while (j <= high)
arr[k++] = temp[j++];
}
❗️ 注意:
-
if (temp[i] <= temp[j]) arr[k++] = temp[i++];
这句代码
if()
中的<=
决定了归并排序算法是稳定的,即不会改变相同元素值的相对次序 -
传入
temp[]
,在主函数内开辟额外数组,可以在递归栈中重复使用,避免了每次递归都要开辟和销毁空间,如:void merge(int arr[],int temp[], int low, int mid, int high) { int* temp = new int[high - low + 1]; // 开辟数组 for (int k = low; k <= high; k++) temp[k] = arr[k]; int i = low, j = mid + 1, k = low; while (i <= mid && j <= high) { if (temp[i] <= temp[j]) arr[k++] = temp[i++]; else arr[k++] = temp[j++]; } while (i <= mid) arr[k++] = temp[i++]; while (j <= high) arr[k++] = temp[j++]; delete[] temp; // 销毁数组 }
算法分析
- 空间复杂度:
O(n)
,辅助数组为n
个单元,递归栈为O(logn)
- 时间复杂度:
O(nlogn)
,每趟归并时间为O(n)
,共需⌈log2n⌉
趟 - 稳定性:✔️
对比 2路归并排序
,可以类比分析 k路归并排序