文章目录
归并排序(Merge Sort)是一种基于分治思想的排序算法,其迭代实现(自底向上)通过逐步合并有序子数组来完成排序,避免了递归调用的开销。
算法原理
1、分阶段:将数组视为由多个长度为1的子数组组成(初始时每个元素自成一个有序子数组)。
2、合阶段:逐步合并相邻的子数组,每次合并后子数组长度翻倍(1→2→4→...),直到整个数组有序。
迭代法步骤
1、初始化:设置子数组的初始长度 size = 1。
2、循环合并:
将数组分成多个长度为 size 的子数组。
合并相邻的两个子数组(每个子数组内部已有序),生成一个长度为 2size 的有序子数组。
如果剩余元素不足 2 size,直接合并剩余部分。
3、更新长度:size *= 2,重复合并直到 size >= n(数组长度)。
代码实现
代码实现1:
bash
#include <stdio.h>
#include <stdlib.h>
// 合并两个有序子数组 arr[left..mid] 和 arr[mid+1..right]
void merge(int arr[], int left, int mid, int right) {
int n1 = mid - left + 1;
int n2 = right - mid;
int *L = (int *)malloc(n1 * sizeof(int));
int *R = (int *)malloc(n2 * sizeof(int));
// 拷贝数据到临时数组
for (int i = 0; i < n1; i++) L[i] = arr[left + i];
for (int j = 0; j < n2; j++) R[j] = arr[mid + 1 + j];
// 合并临时数组回原数组
int i = 0, j = 0, k = left;
while (i < n1 && j < n2) {
if (L[i] <= R[j]) arr[k++] = L[i++];
else arr[k++] = R[j++];
}
while (i < n1) arr[k++] = L[i++];
while (j < n2) arr[k++] = R[j++];
free(L);
free(R);
}
// 迭代法归并排序
void mergeSortIterative(int arr[], int n) {
int size = 1; // 初始子数组长度为1
while (size < n) {
int left = 0;
while (left < n - 1) {
int mid = left + size - 1;
if (mid >= n - 1) mid = n - 1; // 防止越界
int right = (left + 2 * size - 1) < (n - 1) ? (left + 2 * size - 1) : (n - 1);
merge(arr, left, mid, right);
left += 2 * size; // 移动到下一对子数组的起始位置
}
size *= 2; // 子数组长度翻倍
}
}
// 测试代码
int main() {
int arr[] = {12, 11, 13, 5, 6, 7};
int n = sizeof(arr) / sizeof(arr[0]);
printf("原始数组: ");
for (int i = 0; i < n; i++) printf("%d ", arr[i]);
printf("\n");
mergeSortIterative(arr, n);
printf("排序后数组: ");
for (int i = 0; i < n; i++) printf("%d ", arr[i]);
printf("\n");
return 0;
}
代码实现2(摘抄自菜鸟教程):
c
#include <stdio.h>
#include <stdlib.h>
// 函数声明
int min(int x, int y);
void merge_sort(int arr[], int len);
int main() {
int arr[] = { 22, 34, 3, 32, 82, 55, 89, 50, 37, 5, 64, 35, 9, 70 };
int len = sizeof(arr) / sizeof(arr[0]); // 计算数组长度
merge_sort(arr, len); // 调用归并排序函数
// 打印排序后的数组
for (int i = 0; i < len; i++) {
printf("%d ", arr[i]);
}
return 0;
}
// 返回两个数中的最小值
int min(int x, int y) {
return x < y ? x : y;
}
// 归并排序函数
void merge_sort(int arr[], int len) {
int* a = arr;
int* b = (int*) malloc(len * sizeof(int));
if (b == NULL) { // 检查内存分配是否成功
fprintf(stderr, "Memory allocation failed\n");
exit(EXIT_FAILURE);
}
for (int seg = 1; seg < len; seg += seg) {
for (int start = 0; start < len; start += seg + seg) {
int low = start;
int mid = min(start + seg, len);
int high = min(start + seg + seg, len);
int k = low;
int start1 = low, end1 = mid;
int start2 = mid, end2 = high;
// 合并两个子数组
while (start1 < end1 && start2 < end2) {
b[k++] = a[start1] < a[start2] ? a[start1++] : a[start2++];
}
while (start1 < end1) {
b[k++] = a[start1++];
}
while (start2 < end2) {
b[k++] = a[start2++];
}
}
// 交换数组指针
int* temp = a;
a = b;
b = temp;
}
// 如果a和arr不相同,则将a的内容复制回arr
if (a != arr) {
for (int i = 0; i < len; i++) {
b[i] = a[i];
}
b = a;
}
free(b); // 释放内存
}
迭代法 vs 递归法
| 特性 | 迭代法 | 递归法 |
|---|---|---|
| 实现方式 | 通过循环逐步合并子数组 | 通过递归分解问题 |
| 空间开销 | 仅需临时数组空间 | 递归栈空间(可能栈溢出) |
| 代码复杂度 | 稍复杂(需手动管理边界) | 更简洁(分治逻辑直观) |
优化方向
1、小数组优化:当子数组长度较小时(如 size <= 16),改用插入排序以减少常数因子。
2、原地归并:通过复杂指针操作减少临时空间使用(但实现难度高,可能牺牲稳定性)。