C语言基础-十一、递归与分治(完结)

目录

1、递归基础

[1.1 递归的定义](#1.1 递归的定义)

[1.2 递归的工作原理](#1.2 递归的工作原理)

[1.3 递归 vs 循环](#1.3 递归 vs 循环)

[1.4 递归示例:计算阶乘](#1.4 递归示例:计算阶乘)

[1.5 递归调用过程可视化](#1.5 递归调用过程可视化)

2、爬楼梯问题

[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、汉诺塔问题

[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、归并排序

[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、更多递归示例

[5.1 斐波那契数列](#5.1 斐波那契数列)

[5.2 二分查找(递归)](#5.2 二分查找(递归))

[5.3 快速排序(递归)](#5.3 快速排序(递归))

[5.4 全排列(递归)](#5.4 全排列(递归))

6、递归常见问题与优化

[6.1 栈溢出问题](#6.1 栈溢出问题)

[6.2 尾递归优化](#6.2 尾递归优化)

[6.3 递归深度限制](#6.3 递归深度限制)

7、最佳实践总结

8、递归调试技巧

递归 :函数在定义中调用自身的编程技巧

核心思想:将大问题分解为相同结构的小问题,逐层解决

关键要素:递归出口(终止条件)+ 递归调用(问题分解)


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;
}
相关推荐
geovindu2 小时前
python: Template Method Pattern
开发语言·python·设计模式·模板方法模式
We་ct2 小时前
LeetCode 173. 二叉搜索树迭代器:BSTIterator类 实现与解析
前端·算法·leetcode·typescript
weixin_395448912 小时前
main.c_0222cursor
c语言·前端·算法
sycmancia2 小时前
C++——析构函数的调用顺序、const修饰对象、类成员
开发语言·c++
无尽的沉默2 小时前
Thymeleaf 表达式
java·开发语言·前端
Zik----2 小时前
Leetcode27 —— 移除元素(双指针)
数据结构·算法
xhyu612 小时前
【学习笔记】推荐系统 (2.召回:ItemCF、Swing、UserCF)
笔记·学习
【数据删除】3482 小时前
计算机复试学习笔记 Day24【补】
笔记·学习
Java后端的Ai之路2 小时前
【JDK】-JDK 11 新特性内容整理(很全面)
java·开发语言·后端·jdk