算法原理
归并排序是分治法的典型应用,把两个或k个有序的子序列合并为一个。2路归并,2合一。k路归并,k合一。内部排序通常采用2路归并排序,先将数组分成两半,分别排序,然后合并。合并的过程需要将两个有序的子数组合并成一个有序的数组。
递归代码实现
//A[left...mid]和A[mid+1...right]这两个部分各自有序,本函数的作用是将两个部分归并
//B是辅助数组,其元素个数和元素内容与待归并数组A相同
void Merge(int A[],int left,int mid,int right,int B[]){
int i = left;
int j = mid+1;
int k = left;
while(i <= mid && j <= right){
if(A[i]<=A[j])//A[i]和A[j]相等的情况,把A[i]赋给B[k],确保算法稳定性
B[k++] = A[i++];
else
B[k++] = A[j++];
}
while(i <= mid) B[k++] = A[i++];
while(j <= right) B[k++] = A[j++];
//将归并后的数组B转存到A中
for(int p = left;p <=right;p++) A[p] = B[p];
}
//对数组A的[left...right]部分归并排序
//B是辅助数组,其元素个数和元素内容与待归并数组A相同
void MergeSort(int A[],int left,int right,int B[]){
if(left < right){
// 当 left 和 right 的值较大时(例如接近 INT_MAX),left + right 可能导致整数溢出。
// int mid = (left + right) /2;
int mid = left + (right - left)/2;//避免整数溢出
MergeSort(A,left,mid,B); //对左半部分归并排序
MergeSort(A,mid+1,right,B); //对右半部分归并排序
Merge(A,left,mid,right,B); //归并
}
}
非递归(迭代)代码
Merge函数与递归代码中的一模一样。
//A[left...mid]和A[mid+1...right]这两个部分各自有序,本函数的作用是将两个部分归并
//B是辅助数组,其元素个数和元素内容与待归并数组A相同
void Merge(int A[],int left,int mid,int right,int B[]){
int i = left;
int j = mid+1;
int k = left;
while(i <= mid && j <= right){
if(A[i]<=A[j])//A[i]和A[j]相等的情况,把A[i]赋给B[k],确保算法稳定性
B[k++] = A[i++];
else
B[k++] = A[j++];
}
while(i <= mid) B[k++] = A[i++];
while(j <= right) B[k++] = A[j++];
//将归并后的数组B转存到A中
for(int p = left;p <=right;p++) A[p] = B[p];
}
//对数组A做归并排序,数组A中从序号0开始一共存放有n个元素
//B是辅助数组,其元素个数和元素内容与待归并数组A相同
void MergeSortNonRecursive(int A[],int n,int B[]){
//步长从1开始,每次翻倍
for(int step = 1;step < n;step*=2){
for(int left = 0;left < n;left += 2*step){
//计算当块的中间点和右边界
int mid = left + step -1;
int right = (left + 2*step -1 < n) ? (left + 2*step -1) : n-1;
//如果mid>=n-1,说明没有第二个子数组,跳过合并
if(mid >= n-1) break;
//合并两个子数组
Merge(A,left,mid,right,B);
}
}
}
总结
n个元素进行2路归并排序,归并趟数=向上取整(log2n)。
每一趟归并时间复杂度是O(n)。
算法时间复杂度是O(nlog2n)。
时间复杂度 | O(nlogn) |
空间复杂度 | O(n)大小为n的辅助数组 |
稳定性 | 稳定 |
适用性 | 顺序表、链表 |
递归版本与非递归版本的对比
特性 | 递归版本 | 非递归版本 |
---|---|---|
代码结构 | 更简洁 | 需显式控制循环和边界条件 |
空间开销 | 递归栈+辅助数组 | 仅需辅助数组 |
适用场景 | 代码易读性优先 | 避免递归深度过大导致栈溢出 |