目录
[1.1 递归的定义](#1.1 递归的定义)
[1.2 递归的工作原理](#1.2 递归的工作原理)
[1.3 递归 vs 循环](#1.3 递归 vs 循环)
[1.4 递归示例:计算阶乘](#1.4 递归示例:计算阶乘)
[1.5 递归调用过程可视化](#1.5 递归调用过程可视化)
[2.1 问题分析](#2.1 问题分析)
[2.2 递归解法](#2.2 递归解法)
[2.3 递归树分析](#2.3 递归树分析)
[2.4 优化解法1:记忆化递归](#2.4 优化解法1:记忆化递归)
[2.5 优化解法2:动态规划(循环)](#2.5 优化解法2:动态规划(循环))
[2.6 三种解法对比](#2.6 三种解法对比)
[3.1 问题描述](#3.1 问题描述)
[3.2 递归思路](#3.2 递归思路)
[3.3 完整代码实现](#3.3 完整代码实现)
[3.4 执行过程演示(3个圆盘)](#3.4 执行过程演示(3个圆盘))
[3.5 递归调用树分析](#3.5 递归调用树分析)
[3.6 非递归解法(迭代)](#3.6 非递归解法(迭代))
[4.1 算法原理](#4.1 算法原理)
[4.2 算法图示](#4.2 算法图示)
[4.3 完整代码实现](#4.3 完整代码实现)
[4.4 带调试信息的归并排序](#4.4 带调试信息的归并排序)
[4.5 归并排序性能分析](#4.5 归并排序性能分析)
[4.6 排序算法对比](#4.6 排序算法对比)
[5.1 斐波那契数列](#5.1 斐波那契数列)
[5.2 二分查找(递归)](#5.2 二分查找(递归))
[5.3 快速排序(递归)](#5.3 快速排序(递归))
[5.4 全排列(递归)](#5.4 全排列(递归))
[6.1 栈溢出问题](#6.1 栈溢出问题)
[6.2 尾递归优化](#6.2 尾递归优化)
[6.3 递归深度限制](#6.3 递归深度限制)
递归 :函数在定义中调用自身的编程技巧
核心思想:将大问题分解为相同结构的小问题,逐层解决
关键要素:递归出口(终止条件)+ 递归调用(问题分解)
1、递归基础
1.1 递归的定义
cpp
递归 = 函数定义中调用函数本身
两个必要条件:
1. 递归出口(终止条件)- 防止无限递归
2. 递归调用(问题分解)- 向出口靠近
1.2 递归的工作原理
cpp
每次递归调用都会在栈中开辟新的栈帧
┌─────────────────┐
│ main() 栈帧 │
├─────────────────┤
│ func(3) 栈帧 │ ← 第1次调用
├─────────────────┤
│ func(2) 栈帧 │ ← 第2次调用
├─────────────────┤
│ func(1) 栈帧 │ ← 第3次调用(递归出口)
└─────────────────┘
调用过程:入栈(向下)
返回过程:出栈(向上)
1.3 递归 vs 循环
| 特性 | 递归 | 循环 |
|---|---|---|
| 代码简洁性 | ✅ 简洁优雅 | 相对冗长 |
| 内存占用 | 高(栈帧) | 低 |
| 执行效率 | 较低 | ✅ 较高 |
| 栈溢出风险 | ⚠️ 有 | ✅ 无 |
| 适用场景 | 树、图、分治 | 简单迭代 |
1.3.1具体对比
cpp
/*
循环:在同一层代码里反复执行
递归:通过函数调用自己,一层层深入
计算 1+2+3+...+n 的和
*/
//循环写法
int sumIterative(int n) {
int result = 0;
for (int i = 1; i <= n; i++) {
result += i;
}
return result;
}
//递归写法
int sumRecursive(int n) {
if (n == 1) return 1; // 递归出口
return n + sumRecursive(n - 1); // 递归调用
}
翻译数学公式
cpp
// 递归:直接翻译数学公式
// f(n) = n + f(n-1)
int fib(int n) {
if (n <= 1) return n;
return fib(n-1) + fib(n-2);
}
// 循环:需要多个变量
int fib(int n) {
if (n <= 1) return n;
int a = 0, b = 1, temp;
for (int i = 2; i <= n; i++) {
temp = a + b;
a = b;
b = temp;
}
return b;
}
内存占用对比
| 递归 | 循环 |
|---|---|
| 每次调用创建新栈帧 | 只用固定变量 |
| 深度越大,占用越多 | 内存占用不变 |
cpp
递归调用栈示意(n=5):
┌─────────────────┐
│ sumRecursive(5) │ ← 第1层栈帧
├─────────────────┤
│ sumRecursive(4) │ ← 第2层栈帧
├─────────────────┤
│ sumRecursive(3) │ ← 第3层栈帧
├─────────────────┤
│ sumRecursive(2) │ ← 第4层栈帧
├─────────────────┤
│ sumRecursive(1) │ ← 第5层栈帧
└─────────────────┘
循环内存示意:
┌─────────────────┐
│ result = 15 │ ← 固定变量
│ i = 6 │
└─────────────────┘
执行效率
| 递归 | 循环 |
|---|---|
| 函数调用有开销 | 直接跳转,无开销 |
| 需要压栈/出栈 | 只需更新变量 |
cpp
递归的额外开销:
1. 保存当前函数状态(压栈)
2. 跳转到函数开头
3. 恢复函数状态(出栈)
4. 返回到调用点
循环的开销:
1. 更新循环变量
2. 判断条件
3. 跳转回循环开头
循环比递归快约 2-10 倍(取决于场景)
栈溢出风险
cpp
// 递归:n 太大会栈溢出
int factorial(int n) {
if (n <= 1) return 1;
return n * factorial(n - 1);
}
// n = 10000 可能崩溃!
// 循环:n 多大都没事
int factorial(int n) {
int result = 1;
for (int i = 2; i <= n; i++) {
result *= i;
}
return result;
}
// n = 10000 也能运行(虽然结果可能溢出)
/*
栈溢出演示:
递归深度限制 ≈ 1000-10000 层(取决于系统)
┌─────────────────┐
│ │
│ 栈空间有限 │ ← 通常 1-8 MB
│ (8MB) │
│ │
│ 递归层数太多 │
│ 栈被撑爆了! │ ← 栈溢出
│ │
└─────────────────┘
*/
1.3.2 适用场景
| 递归 | 循环 |
|---|---|
| 树/图遍历 | 简单重复任务 |
| 分治算法 | 固定次数迭代 |
| 回溯问题 | 性能敏感场景 |
| 数学归纳 | 大数据量处理 |
什么时候用递归?什么时候用循环?
优先用递归的场景
cpp
数据结构本身是递归定义的
- 树(文件夹结构、DOM树)
- 图(迷宫、社交网络)
问题天然适合分治
- 归并排序
- 快速排序
- 汉诺塔
代码可读性更重要
- 递归代码更接近数学公式
- 团队能理解递归逻辑
cpp
// 树的遍历:递归更自然
void traverseTree(Node* node) {
if (node == NULL) return;
printf("%d ", node->value);
traverseTree(node->left);
traverseTree(node->right);
}
// 用循环写树遍历->需要手动维护栈,代码复杂
优先用循环的场景
cpp
简单的重复任务
- 遍历数组
- 累加求和
- 查找元素
性能要求高
- 大数据量处理
- 实时系统
递归深度不可控
- 用户输入决定深度
- 可能超过栈限制
// 数组遍历:循环更合适
for (int i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
// 用递归写->没必要,还浪费栈空间
1.3.3、递归和循环可以互相转换
任何递归都可以改成循环(但可能复杂)
cpp
// 递归版本
void func(int n) {
if (n <= 0) return;
printf("%d ", n);
func(n - 1);
}
// 循环版本(等价)
void func(int n) {
while (n > 0) {
printf("%d ", n);
n--;
}
}
复杂递归转循环需要手动维护栈
cpp
// 递归:汉诺塔
void hanoi(int n, char from, char buffer, char to) {
if (n == 1) {
printf("%c -> %c\n", from, to);
return;
}
hanoi(n-1, from, to, buffer);
printf("%c -> %c\n", from, to);
hanoi(n-1, buffer, from, to);
}
// 循环:需要手动模拟栈(代码复杂很多)
// 需要几十行代码才能实现相同功能
1.3.4、性能对比实测
cpp
#include <stdio.h>
#include <time.h>
// 递归斐波那契(慢)
long long fibRecursive(int n) {
if (n <= 1) return n;
return fibRecursive(n-1) + fibRecursive(n-2);
}
// 循环斐波那契(快)
long long fibIterative(int n) {
if (n <= 1) return n;
long long a = 0, b = 1;
for (int i = 2; i <= n; i++) {
long long temp = a + b;
a = b;
b = temp;
}
return b;
}
int main() {
int n = 40;
clock_t start = clock();
fibRecursive(n);
clock_t end = clock();
printf("递归: %.2f 秒\n", (double)(end-start)/CLOCKS_PER_SEC);
start = clock();
fibIterative(n);
end = clock();
printf("循环: %.6f 秒\n", (double)(end-start)/CLOCKS_PER_SEC);
return 0;
}
/*
递归: 1.23 秒
循环: 0.000001 秒
循环比递归快约 100 万倍!(这个例子中)
*/
1.3.5、决策树:选递归还是循环?
cpp
开始
│
▼
问题是树/图/分治吗?
╱ ╲
是 否
│ │
▼ ▼
用递归 性能要求高吗?
╱ ╲
是 否
│ │
▼ ▼
用循环 递归更清晰吗?
╱ ╲
是 否
│ │
▼ ▼
用递归 用循环
1.3.6、总结
cpp
✅ 推荐做法:
能用循环的简单问题,优先用循环
树/图/分治等复杂问题,用递归
递归深度可能很大时,改用循环
性能关键代码,用循环
递归时一定要有出口,防止无限递归
❌ 避免做法:
用递归做简单循环能做的事
递归没有出口或出口条件错误
在嵌入式/内存受限环境用深度递归
不考虑栈溢出风险
1.4 递归示例:计算阶乘
cpp
#include <stdio.h>
// 递归版本
long long factorialRecursive(int n) {
// 递归出口
if (n <= 1) {
return 1;
}
// 递归调用
return n * factorialRecursive(n - 1);
}
// 循环版本(对比)
long long factorialIterative(int n) {
long long result = 1;
for (int i = 2; i <= n; i++) {
result *= i;
}
return result;
}
int main() {
int n = 5;
printf("递归计算 %d! = %lld\n", n, factorialRecursive(n));
printf("循环计算 %d! = %lld\n", n, factorialIterative(n));
// 递归调用过程演示
// factorialRecursive(5)
// = 5 * factorialRecursive(4)
// = 5 * 4 * factorialRecursive(3)
// = 5 * 4 * 3 * factorialRecursive(2)
// = 5 * 4 * 3 * 2 * factorialRecursive(1)
// = 5 * 4 * 3 * 2 * 1
// = 120
return 0;
}
1.5 递归调用过程可视化
cpp
#include <stdio.h>
// 带调试信息的递归
void factorialDebug(int n, int depth) {
// 打印缩进,显示递归深度
for (int i = 0; i < depth; i++) printf(" ");
printf("进入 factorial(%d)\n", n);
if (n <= 1) {
for (int i = 0; i < depth; i++) printf(" ");
printf("返回 1(递归出口)\n");
return;
}
factorialDebug(n - 1, depth + 1);
for (int i = 0; i < depth; i++) printf(" ");
printf("返回 %d * factorial(%d)\n", n, n - 1);
}
int main() {
printf("=== 递归调用过程 ===\n");
factorialDebug(4, 0);
return 0;
}
/* 输出:
进入 factorial(4)
进入 factorial(3)
进入 factorial(2)
进入 factorial(1)
返回 1(递归出口)
返回 2 * factorial(1)
返回 3 * factorial(2)
返回 4 * factorial(3)
*/
2、爬楼梯问题
2.1 问题分析
cpp
问题:有n阶楼梯,每次可以走1阶或2阶,问有多少种走法?
分析:
- 第n阶可以从第(n-1)阶走1步到达
- 第n阶可以从第(n-2)阶走2步到达
- 所以:f(n) = f(n-1) + f(n-2)
这实际上是斐波那契数列!
边界条件:
- f(1) = 1(只有1种走法:走1阶)
- f(2) = 2(有2种走法:1+1 或 2)
2.2 递归解法
cpp
#include <stdio.h>
// 递归解法
int climbStairsRecursive(int n) {
// 递归出口
if (n == 1) {
return 1;
}
if (n == 2) {
return 2;
}
// 递归调用
return climbStairsRecursive(n - 1) + climbStairsRecursive(n - 2);
}
int main() {
int n = 5;
printf("%d阶楼梯有 %d 种走法\n", n, climbStairsRecursive(n));
// 打印1-10阶的结果
printf("\n楼梯数\t走法数\n");
for (int i = 1; i <= 10; i++) {
printf("%d\t%d\n", i, climbStairsRecursive(i));
}
return 0;
}
2.3 递归树分析
cpp
计算 climbStairsRecursive(5) 的递归树:
f(5)
/ \
f(4) f(3)
/ \ / \
f(3) f(2) f(2) f(1)
/ \
f(2) f(1)
问题:存在大量重复计算!
f(3) 被计算2次
f(2) 被计算3次
f(1) 被计算2次
时间复杂度:O(2^n) - 指数级,效率低
空间复杂度:O(n) - 递归栈深度
2.4 优化解法1:记忆化递归
cpp
#include <stdio.h>
#include <string.h>
#define MAX_N 100
int memo[MAX_N]; // 记忆数组
int climbStairsMemo(int n) {
// 递归出口
if (n == 1) return 1;
if (n == 2) return 2;
// 如果已经计算过,直接返回
if (memo[n] != 0) {
return memo[n];
}
// 计算并存储结果
memo[n] = climbStairsMemo(n - 1) + climbStairsMemo(n - 2);
return memo[n];
}
int main() {
memset(memo, 0, sizeof(memo)); // 初始化记忆数组
int n = 50; // 递归解法无法计算这么大的数
printf("%d阶楼梯有 %d 种走法\n", n, climbStairsMemo(n));
return 0;
}
2.5 优化解法2:动态规划(循环)
cpp
#include <stdio.h>
// 动态规划解法
int climbStairsDP(int n) {
if (n == 1) return 1;
if (n == 2) return 2;
int prev2 = 1; // f(n-2)
int prev1 = 2; // f(n-1)
int current;
for (int i = 3; i <= n; i++) {
current = prev1 + prev2;
prev2 = prev1;
prev1 = current;
}
return current;
}
int main() {
int n = 50;
printf("%d阶楼梯有 %d 种走法\n", n, climbStairsDP(n));
// 性能对比
printf("\n=== 性能对比 ===\n");
printf("递归解法:O(2^n) - n=40就需要约1秒\n");
printf("记忆化递归:O(n) - n=100瞬间完成\n");
printf("动态规划:O(n) - n=100瞬间完成\n");
return 0;
}
2.6 三种解法对比
| 解法 | 时间复杂度 | 空间复杂度 | 优点 | 缺点 |
|---|---|---|---|---|
| 纯递归 | O(2^n) | O(n) | 代码简洁 | 效率极低 |
| 记忆化递归 | O(n) | O(n) | 保留递归思路 | 需要额外空间 |
| 动态规划 | O(n) | O(1) | 效率最高 | 思路转换大 |
3、汉诺塔问题
3.1 问题描述
cpp
汉诺塔(Tower of Hanoi):
- 有3根柱子:A(源)、B(辅助)、C(目标)
- n个大小不同的圆盘,按大小顺序叠放在A柱上
- 目标:将所有圆盘从A移动到C
规则:
1. 每次只能移动一个圆盘
2. 大圆盘不能放在小圆盘上面
3. 可以借助B柱作为临时存放
3.2 递归思路
cpp
移动n个圆盘从A到C(借助B):
步骤1:将前(n-1)个圆盘从A移动到B(借助C)
步骤2:将第n个圆盘从A移动到C
步骤3:将(n-1)个圆盘从B移动到C(借助A)
递归出口:n=1时,直接移动
数学关系:
- 移动次数:2^n - 1
- 3个圆盘:7次
- 4个圆盘:15次
- 5个圆盘:31次
- 64个圆盘:约5845亿年(传说世界末日)
3.3 完整代码实现
cpp
#include <stdio.h>
int moveCount = 0; // 记录移动次数
// 移动单个圆盘
void moveDisk(char from, char to, int disk) {
moveCount++;
printf("第%2d步: 圆盘%d 从 %c 移动到 %c\n", moveCount, disk, from, to);
}
// 汉诺塔递归函数
void hanoi(int n, char from, char buffer, char to) {
if (n == 1) {
// 递归出口:只有一个圆盘,直接移动
moveDisk(from, to, n);
} else {
// 步骤1:将前(n-1)个圆盘从from移动到buffer(借助to)
hanoi(n - 1, from, to, buffer);
// 步骤2:将第n个圆盘从from移动到to
moveDisk(from, to, n);
// 步骤3:将(n-1)个圆盘从buffer移动到to(借助from)
hanoi(n - 1, buffer, from, to);
}
}
int main() {
int n = 4;
printf("=== %d个圆盘的汉诺塔问题 ===\n\n", n);
hanoi(n, 'A', 'B', 'C');
printf("\n总共移动 %d 步\n", moveCount);
printf("理论最少步数:%d\n", (1 << n) - 1); // 2^n - 1
return 0;
}
3.4 执行过程演示(3个圆盘)
cpp
=== 3个圆盘的汉诺塔问题 ===
第 1步: 圆盘1 从 A 移动到 C
第 2步: 圆盘2 从 A 移动到 B
第 3步: 圆盘1 从 C 移动到 B
第 4步: 圆盘3 从 A 移动到 C
第 5步: 圆盘1 从 B 移动到 A
第 6步: 圆盘2 从 B 移动到 C
第 7步: 圆盘1 从 A 移动到 C
总共移动 7 步
理论最少步数:7
可视化过程:
初始状态:
A: [1,2,3] B: [] C: []
第1步后:
A: [2,3] B: [] C: [1]
第2步后:
A: [3] B: [2] C: [1]
第3步后:
A: [3] B: [1,2] C: []
第4步后:
A: [] B: [1,2] C: [3]
第5步后:
A: [1] B: [2] C: [3]
第6步后:
A: [1] B: [] C: [2,3]
第7步后:
A: [] B: [] C: [1,2,3] ✓ 完成!
3.5 递归调用树分析
cpp
hanoi(3, A, B, C)
│
├── hanoi(2, A, C, B)
│ ├── hanoi(1, A, B, C) → A→C
│ ├── move: A→B
│ └── hanoi(1, C, A, B) → C→B
│
├── move: A→C
│
└── hanoi(2, B, A, C)
├── hanoi(1, B, C, A) → B→A
├── move: B→C
└── hanoi(1, A, B, C) → A→C
3.6 非递归解法(迭代)
cpp
#include <stdio.h>
#include <stdlib.h>
// 栈结构用于非递归实现
typedef struct {
int n;
char from, buffer, to;
int state; // 执行状态
} Frame;
void hanoiIterative(int n) {
Frame *stack = (Frame*)malloc(1000 * sizeof(Frame));
int top = 0;
int moveCount = 0;
// 初始帧
stack[top++] = (Frame){n, 'A', 'B', 'C', 0};
while (top > 0) {
Frame *f = &stack[top - 1];
if (f->n == 1) {
moveCount++;
printf("第%2d步: 从 %c 移动到 %c\n", moveCount, f->from, f->to);
top--;
continue;
}
switch (f->state) {
case 0:
f->state = 1;
stack[top++] = (Frame){f->n - 1, f->from, f->to, f->buffer, 0};
break;
case 1:
f->state = 2;
moveCount++;
printf("第%2d步: 从 %c 移动到 %c\n", moveCount, f->from, f->to);
break;
case 2:
f->state = 3;
stack[top++] = (Frame){f->n - 1, f->buffer, f->from, f->to, 0};
break;
case 3:
top--;
break;
}
}
free(stack);
printf("总共移动 %d 步\n", moveCount);
}
int main() {
hanoiIterative(3);
return 0;
}
4、归并排序
4.1 算法原理
cpp
归并排序(Merge Sort):分治法的经典应用
核心思想:
1. 分解:将数组分成两半
2. 解决:递归地对两半分别排序
3. 合并:将两个有序数组合并成一个
时间复杂度:O(n log n) - 所有情况下都稳定
空间复杂度:O(n) - 需要临时数组
稳定性:稳定排序
4.2 算法图示
cpp
初始数组:[38, 27, 43, 3, 9, 82, 10]
分解过程:
[38, 27, 43, 3, 9, 82, 10]
↓
[38, 27, 43, 3] [9, 82, 10]
↓ ↓
[38, 27] [43, 3] [9, 82] [10]
↓ ↓ ↓ ↓
[38] [27] [43] [3] [9] [82] [10]
合并过程:
[27, 38] [3, 43] [9, 82] [10]
↓ ↓ ↓ ↓
[3, 27, 38, 43] [9, 10, 82]
↓
[3, 9, 10, 27, 38, 43, 82] ✓ 完成!
4.3 完整代码实现
cpp
#include <stdio.h>
#include <stdlib.h>
// 合并两个有序数组
void merge(int arr[], int left, int mid, int right) {
int n1 = mid - left + 1; // 左半部分大小
int n2 = right - mid; // 右半部分大小
// 创建临时数组
int *leftArr = (int*)malloc(n1 * sizeof(int));
int *rightArr = (int*)malloc(n2 * sizeof(int));
if (leftArr == NULL || rightArr == NULL) {
printf("内存分配失败!\n");
return;
}
// 复制数据到临时数组
for (int i = 0; i < n1; i++) {
leftArr[i] = arr[left + i];
}
for (int j = 0; j < n2; j++) {
rightArr[j] = arr[mid + 1 + j];
}
// 合并临时数组回原数组
int i = 0, j = 0, k = left;
while (i < n1 && j < n2) {
if (leftArr[i] <= rightArr[j]) {
arr[k++] = leftArr[i++];
} else {
arr[k++] = rightArr[j++];
}
}
// 复制剩余元素
while (i < n1) {
arr[k++] = leftArr[i++];
}
while (j < n2) {
arr[k++] = rightArr[j++];
}
free(leftArr);
free(rightArr);
}
// 归并排序递归函数
void mergeSort(int arr[], int left, int right) {
if (left < right) {
// 计算中间位置(防止溢出)
int mid = left + (right - left) / 2;
// 递归排序左半部分
mergeSort(arr, left, mid);
// 递归排序右半部分
mergeSort(arr, mid + 1, right);
// 合并两个有序部分
merge(arr, left, mid, right);
}
}
// 打印数组
void printArray(int arr[], int size) {
printf("[");
for (int i = 0; i < size; i++) {
printf("%d", arr[i]);
if (i < size - 1) printf(", ");
}
printf("]\n");
}
int main() {
int arr[] = {38, 27, 43, 3, 9, 82, 10};
int n = sizeof(arr) / sizeof(arr[0]);
printf("排序前: ");
printArray(arr, n);
mergeSort(arr, 0, n - 1);
printf("排序后: ");
printArray(arr, n);
return 0;
}
4.4 带调试信息的归并排序
cpp
#include <stdio.h>
#include <stdlib.h>
void merge(int arr[], int left, int mid, int right, int depth) {
int n1 = mid - left + 1;
int n2 = right - mid;
int *leftArr = (int*)malloc(n1 * sizeof(int));
int *rightArr = (int*)malloc(n2 * sizeof(int));
for (int i = 0; i < n1; i++) leftArr[i] = arr[left + i];
for (int j = 0; j < n2; j++) rightArr[j] = arr[mid + 1 + j];
// 打印合并信息
for (int i = 0; i < depth; i++) printf(" ");
printf("合并: [");
for (int i = 0; i < n1; i++) printf("%d ", leftArr[i]);
printf("] + [");
for (int j = 0; j < n2; j++) printf("%d ", rightArr[j]);
printf("]\n");
int i = 0, j = 0, k = left;
while (i < n1 && j < n2) {
arr[k++] = (leftArr[i] <= rightArr[j]) ? leftArr[i++] : rightArr[j++];
}
while (i < n1) arr[k++] = leftArr[i++];
while (j < n2) arr[k++] = rightArr[j++];
free(leftArr);
free(rightArr);
}
void mergeSortDebug(int arr[], int left, int right, int depth) {
if (left < right) {
int mid = left + (right - left) / 2;
for (int i = 0; i < depth; i++) printf(" ");
printf("分解: [%d, %d]\n", left, right);
mergeSortDebug(arr, left, mid, depth + 1);
mergeSortDebug(arr, mid + 1, right, depth + 1);
merge(arr, left, mid, right, depth);
}
}
int main() {
int arr[] = {38, 27, 43, 3};
int n = sizeof(arr) / sizeof(arr[0]);
printf("=== 归并排序过程 ===\n\n");
mergeSortDebug(arr, 0, n - 1, 0);
printf("\n排序结果: ");
for (int i = 0; i < n; i++) printf("%d ", arr[i]);
printf("\n");
return 0;
}
4.5 归并排序性能分析
cpp
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
// 归并排序
void merge(int arr[], int left, int mid, int right) {
int n1 = mid - left + 1;
int n2 = right - mid;
int *leftArr = (int*)malloc(n1 * sizeof(int));
int *rightArr = (int*)malloc(n2 * sizeof(int));
for (int i = 0; i < n1; i++) leftArr[i] = arr[left + i];
for (int j = 0; j < n2; j++) rightArr[j] = arr[mid + 1 + j];
int i = 0, j = 0, k = left;
while (i < n1 && j < n2) {
arr[k++] = (leftArr[i] <= rightArr[j]) ? leftArr[i++] : rightArr[j++];
}
while (i < n1) arr[k++] = leftArr[i++];
while (j < n2) arr[k++] = rightArr[j++];
free(leftArr);
free(rightArr);
}
void mergeSort(int arr[], int left, int right) {
if (left < right) {
int mid = left + (right - left) / 2;
mergeSort(arr, left, mid);
mergeSort(arr, mid + 1, right);
merge(arr, left, mid, right);
}
}
// 冒泡排序(对比)
void bubbleSort(int arr[], int n) {
for (int i = 0; i < n - 1; i++) {
for (int j = 0; j < n - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
// 生成随机数组
void generateRandomArray(int arr[], int n) {
for (int i = 0; i < n; i++) {
arr[i] = rand() % 10000;
}
}
// 复制数组
void copyArray(int src[], int dest[], int n) {
for (int i = 0; i < n; i++) {
dest[i] = src[i];
}
}
int main() {
srand(time(NULL));
int sizes[] = {100, 500, 1000, 5000};
int numTests = sizeof(sizes) / sizeof(sizes[0]);
printf("=== 排序算法性能对比 ===\n\n");
printf("%-10s %-15s %-15s\n", "数据量", "归并排序(ms)", "冒泡排序(ms)");
printf("----------------------------------------\n");
for (int i = 0; i < numTests; i++) {
int n = sizes[i];
int *arr1 = (int*)malloc(n * sizeof(int));
int *arr2 = (int*)malloc(n * sizeof(int));
generateRandomArray(arr1, n);
copyArray(arr1, arr2, n);
// 归并排序
clock_t start = clock();
mergeSort(arr1, 0, n - 1);
clock_t end = clock();
double mergeTime = (double)(end - start) / CLOCKS_PER_SEC * 1000;
// 冒泡排序(只对小数据量测试)
double bubbleTime = 0;
if (n <= 1000) {
start = clock();
bubbleSort(arr2, n);
end = clock();
bubbleTime = (double)(end - start) / CLOCKS_PER_SEC * 1000;
}
printf("%-10d %-15.2f %-15.2f\n", n, mergeTime, bubbleTime);
free(arr1);
free(arr2);
}
return 0;
}
4.6 排序算法对比
| 算法 | 平均时间 | 最好时间 | 最坏时间 | 空间 | 稳定性 |
|---|---|---|---|---|---|
| 归并排序 | O(n log n) | O(n log n) | O(n log n) | O(n) | 稳定 |
| 快速排序 | O(n log n) | O(n log n) | O(n²) | O(log n) | 不稳定 |
| 堆排序 | O(n log n) | O(n log n) | O(n log n) | O(1) | 不稳定 |
| 冒泡排序 | O(n²) | O(n) | O(n²) | O(1) | 稳定 |
| 插入排序 | O(n²) | O(n) | O(n²) | O(1) | 稳定 |
稳定性说明:稳定排序:相同元素的先后顺序 不变
不稳定排序:相同元素的先后顺序 可能改变
5、更多递归示例
5.1 斐波那契数列
cpp
#include <stdio.h>
// 递归版本(效率低)
int fibRecursive(int n) {
if (n <= 1) return n;
return fibRecursive(n - 1) + fibRecursive(n - 2);
}
// 记忆化版本
int memo[100];
int fibMemo(int n) {
if (n <= 1) return n;
if (memo[n] != 0) return memo[n];
memo[n] = fibMemo(n - 1) + fibMemo(n - 2);
return memo[n];
}
// 迭代版本(推荐)
int fibIterative(int n) {
if (n <= 1) return n;
int a = 0, b = 1;
for (int i = 2; i <= n; i++) {
int temp = a + b;
a = b;
b = temp;
}
return b;
}
int main() {
printf("斐波那契数列前10项:\n");
for (int i = 0; i < 10; i++) {
printf("%d ", fibIterative(i));
}
printf("\n");
return 0;
}
5.2 二分查找(递归)
cpp
#include <stdio.h>
// 递归二分查找
int binarySearchRecursive(int arr[], int left, int right, int target) {
if (left > right) {
return -1; // 未找到
}
int mid = left + (right - left) / 2;
if (arr[mid] == target) {
return mid;
} else if (arr[mid] > target) {
return binarySearchRecursive(arr, left, mid - 1, target);
} else {
return binarySearchRecursive(arr, mid + 1, right, target);
}
}
int main() {
int arr[] = {1, 3, 5, 7, 9, 11, 13, 15, 17, 19};
int n = sizeof(arr) / sizeof(arr[0]);
int target = 7;
int index = binarySearchRecursive(arr, 0, n - 1, target);
if (index != -1) {
printf("找到 %d,索引: %d\n", target, index);
} else {
printf("未找到 %d\n", target);
}
return 0;
}
5.3 快速排序(递归)
cpp
#include <stdio.h>
// 分区函数
int partition(int arr[], int low, int high) {
int pivot = arr[high];
int i = low - 1;
for (int j = low; j < high; j++) {
if (arr[j] <= pivot) {
i++;
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
int temp = arr[i + 1];
arr[i + 1] = arr[high];
arr[high] = temp;
return i + 1;
}
// 快速排序递归函数
void quickSort(int arr[], int low, int high) {
if (low < high) {
int pi = partition(arr, low, high);
quickSort(arr, low, pi - 1);
quickSort(arr, pi + 1, high);
}
}
int main() {
int arr[] = {10, 7, 8, 9, 1, 5};
int n = sizeof(arr) / sizeof(arr[0]);
quickSort(arr, 0, n - 1);
printf("排序后: ");
for (int i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
printf("\n");
return 0;
}
5.4 全排列(递归)
cpp
#include <stdio.h>
// 交换函数
void swap(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
}
// 全排列递归函数
void permute(int arr[], int start, int end) {
if (start == end) {
// 打印一种排列
for (int i = 0; i <= end; i++) {
printf("%d ", arr[i]);
}
printf("\n");
} else {
for (int i = start; i <= end; i++) {
swap(&arr[start], &arr[i]);
permute(arr, start + 1, end);
swap(&arr[start], &arr[i]); // 回溯
}
}
}
int main() {
int arr[] = {1, 2, 3};
int n = sizeof(arr) / sizeof(arr[0]);
printf("1,2,3的全排列:\n");
permute(arr, 0, n - 1);
return 0;
}
6、递归常见问题与优化
6.1 栈溢出问题
cpp
#include <stdio.h>
// 危险:没有递归出口
void infiniteRecursion() {
infiniteRecursion(); // 栈溢出!
}
// 正确:有明确的递归出口
void safeRecursion(int n) {
if (n <= 0) return; // 递归出口
safeRecursion(n - 1);
}
int main() {
// infiniteRecursion(); // 不要运行!
safeRecursion(10);
printf("安全递归完成\n");
return 0;
}
6.2 尾递归优化
cpp
// 普通递归(非尾递归)
int factorial1(int n) {
if (n <= 1) return 1;
return n * factorial1(n - 1); // 最后一步是乘法
}
// 尾递归
int factorialHelper(int n, int acc) {
if (n <= 1) return acc;
return factorialHelper(n - 1, n * acc); // 最后一步是函数调用
}
int factorial2(int n) {
return factorialHelper(n, 1);
}
// 注意:C编译器通常不优化尾递归,但代码更清晰
6.3 递归深度限制
cpp
#include <stdio.h>
void checkDepth(int depth) {
if (depth % 100 == 0) {
printf("当前深度: %d\n", depth);
}
checkDepth(depth + 1);
}
int main() {
// 不同系统的栈大小不同
// 通常递归深度在1000-10000之间会栈溢出
// checkDepth(0); // 谨慎测试
return 0;
}
7、最佳实践总结
✅ 递归使用原则:
1. 必须有明确的递归出口
2. 每次递归必须向出口靠近
3. 避免过深的递归(考虑迭代替代)
4. 注意重复计算(使用记忆化)
5. 考虑栈空间限制
✅ 适合递归的场景:
1. 树和图的遍历
2. 分治算法(归并、快速排序)
3. 回溯算法(全排列、N皇后)
4. 数学归纳问题(阶乘、斐波那契)
❌ 不适合递归的场景:
1. 简单循环可解决的问题
2. 递归深度不可控
3. 性能要求极高的场景
4. 存在大量重复计算
8、递归调试技巧
cpp
#include <stdio.h>
// 带深度信息的调试
void debugRecursion(int n, int depth) {
// 打印缩进
for (int i = 0; i < depth; i++) printf(" ");
printf("进入 n=%d\n", n);
if (n <= 1) {
for (int i = 0; i < depth; i++) printf(" ");
printf("返回 1(出口)\n");
return;
}
debugRecursion(n - 1, depth + 1);
for (int i = 0; i < depth; i++) printf(" ");
printf("返回 n=%d\n", n);
}
int main() {
debugRecursion(4, 0);
return 0;
}