

在算法世界里,排序算法是基础中的基础,而归并排序凭借其稳定的时间复杂度和清晰的逻辑结构,成为很多开发者学习递归思想的首选案例。今天我们就以 C 语言为工具,从零拆解归并排序的递归实现,带你理解 “分治思想” 如何落地为可执行的代码。
归并排序的核心逻辑可以用 “分而治之” 四个字概括:
这种思想的优势在于时间复杂度的稳定性 —— 无论原始数组是否有序,归并排序的时间复杂度始终是O(nlogn),这比冒泡排序、插入排序等 O (n²) 级别的算法在大数据量下快得多。而递归,则是实现 “分” 操作最直观的方式。
我们先来看完整的 C 语言代码(基于示例优化补充),再逐段拆解关键部分:
#include<stdio.h>
#define A 10000 // 定义临时数组最大长度,避免栈溢出
// 归并排序递归函数:负责拆分与合并
void merge_sort(int arr[], int left, int right) {
// 递归终止条件:当子数组只有1个元素时(left >= right),无需拆分
if (left >= right)
return;
// 1. 分:计算中间索引,避免直接(left+right)/2导致的溢出
int mid = left + (right - left) / 2;
// 递归拆分左子数组 [left, mid]
merge_sort(arr, left, mid);
// 递归拆分右子数组 [mid+1, right]
merge_sort(arr, mid + 1, right);
// 2. 治:合并两个有序子数组
int tmp[A]; // 临时数组,存储合并后的有序元素
int k = 0; // 临时数组的索引指针
int i = left; // 左子数组的起始索引
int j = mid + 1; // 右子数组的起始索引
// 比较两个子数组的元素,按从小到大存入临时数组
while (i <= mid && j <= right) {
if (arr[i] <= arr[j]) {
tmp[k++] = arr[i++]; // 左子数组元素更小,存入临时数组并后移指针
} else {
tmp[k++] = arr[j++]; // 右子数组元素更小,存入临时数组并后移指针
}
}
// 将左子数组中剩余的元素存入临时数组
while (i <= mid) {
tmp[k++] = arr[i++];
}
// 将右子数组中剩余的元素存入临时数组
while (j <= right) {
tmp[k++] = arr[j++];
}
// 3. 把临时数组的有序元素拷贝回原数组的 [left, right] 区间
for (int c = 0; c < k; c++) {
arr[left + c] = tmp[c];
}
}
int main() {
int n = 0;
// 输入数组长度
printf("请输入数组中数的个数:");
scanf("%d", &n);
// 定义变长数组(C99支持),存储待排序元素
int arr[n];
// 输入数组元素
printf("请输入数组的数(用空格分隔):");
for (int a = 0; a < n; a++) {
scanf("%d", &arr[a]);
}
// 调用归并排序:排序区间为整个数组 [0, n-1]
merge_sort(arr, 0, n - 1);
// 输出排序后的结果
printf("排序后的数组:");
for (int b = 0; b < n; b++) {
printf("%d ", arr[b]);
}
return 0;
}
merge_sort函数是整个算法的灵魂,它的参数arr是待排序数组,left和right分别代表当前排序区间的左右边界。
拆分完成后,就进入 “治” 的环节 —— 把两个有序的子数组合并为一个有序数组。这一步需要借助临时数组tmp,避免直接在原数组上操作导致数据丢失。
合并过程分为三步:
假设我们输入:
请输入数组中数的个数:5
请输入数组的数(用空格分隔):3 1 4 2 5
代码会经过递归拆分(拆分为[3]、[1]、[4]、[2]、[5]),再逐步合并:
最终输出:排序后的数组:1 2 3 4 5,符合预期。
示例代码虽能正常运行,但仍有提升空间:
通过 C 语言实现归并排序,我们不仅学会了一种高效的排序算法,更理解了递归思想的本质 ——将复杂问题拆解为多个相同的子问题,解决子问题后再整合结果。递归的关键在于明确 “终止条件” 和 “子问题边界”,而归并排序的 “分” 与 “治”,正是这一思想的完美体现。
如果你是算法初学者,建议多调试代码:在merge_sort函数中加入printf语句,观察left、mid、right的变化,就能直观看到递归的拆分过程。动手实践后,你会发现 “递归” 不再是抽象的概念,而是可以掌控的代码逻辑。