

递归(Recursion)是指在函数内部调用自身的一种编程技术。在C语言中,递归被广泛应用于解决一些可以分解为相似子问题的任务。在 C语言中,递归是指一个函数在其函数体内部直接或间接地调用自身的编程技巧。简单来说,就是函数自己调用自己来解决问题。
递归的关键点在于:
递归可以理解为一种分而治之的思想:将复杂问题拆分为若干规模更小但相似的子问题,直到可以直接解决。
递归函数通常由以下两个部分组成:
#include <stdio.h>
void printNumbers(int n) {
if (n == 0) {
return; // 基准条件
}
printf("%d\n", n);
printNumbers(n - 1); // 递归关系
}
int main() {
printNumbers(5);
return 0;
}输出:
5
4
3
2
1递归函数的执行本质上依赖于函数调用栈(Call Stack)。在每次递归调用时,当前函数的状态被保存到栈中,以便返回后继续执行。
递归的每次调用会占用一定的内存空间(栈帧)。如果递归深度过大,可能导致栈溢出(Stack Overflow)。
以阶乘函数为例,当计算factorial(3)时,首先会进入函数,因为3不等于 0,所以执行return 3 * factorial(2)。此时,系统会为factorial(2)开辟一个新的栈帧,记录相关信息。接着在factorial(2)中,因为2不等于 0,执行return 2 * factorial(1),又会开辟一个新的栈帧。在factorial(1)中,执行return1 * factorial(0),再开辟一个栈帧。当factorial(0)被调用时,满足递归基例,返回 1。 然后,factorial(1)可以根据return 1 * factorial(0)计算出结果为 1,factorial(2)根据return2*factorial(1)计算出结果为 2,factorial(3)根据return 3 * factorial(2)计算出结果为6。这个过程就是从递归基例开始逐步回溯计算出最终结果的过程。
优点:
缺点: 1.递归函数在每次调用自身时都会消耗一定的栈空间来存储栈帧。如果递归的深度过大(即函数自己调用自己的次数过多),可能会导致栈溢出。例如,在计算一个非常大的整数的阶乘时,如果使用简单的递归函数,可能会因为栈空间不足而导致程序崩溃。 2.递归函数的执行效率有时可能不如非递归函数。因为递归涉及到函数调用的开销,包括参数传递、栈帧的开辟和销毁等操作。在一些性能要求较高的场景下,可能需要考虑将递归函数转换为非递归函数来提高效率。
问题描述:计算正整数的阶乘,即 n! = n × (n-1) × ... × 1。
#include <stdio.h>
int factorial(int n) {
if (n == 0 || n == 1) {
return 1; // 基准条件
}
return n * factorial(n - 1); // 递归关系
}
int main() {
printf("Factorial of 5: %d\n", factorial(5));
return 0;
}输出:
Factorial of 5: 120问题描述:计算斐波那契数列的第 n 项。
#include <stdio.h>
int fibonacci(int n) {
if (n == 0) return 0; // 基准条件
if (n == 1) return 1; // 基准条件
return fibonacci(n - 1) + fibonacci(n - 2); // 递归关系
}
int main() {
printf("Fibonacci(10): %d\n", fibonacci(10));
return 0;
}输出:
Fibonacci(10): 55问题描述:将盘子从起始柱移动到目标柱,满足每次只能移动一个盘子,且大盘子不能放在小盘子上。
#include <stdio.h>
void hanoi(int n, char from, char to, char aux) {
if (n == 1) {
printf("Move disk 1 from %c to %c\n", from, to);
return;
}
hanoi(n - 1, from, aux, to); // 移动 n-1 个盘子到辅助柱
printf("Move disk %d from %c to %c\n", n, from, to);
hanoi(n - 1, aux, to, from); // 移动 n-1 个盘子到目标柱
}
int main() {
hanoi(3, 'A', 'C', 'B');
return 0;
}输出:
Move disk 1 from A to C
Move disk 2 from A to B
Move disk 1 from C to B
Move disk 3 from A to C
Move disk 1 from B to A
Move disk 2 from B to C
Move disk 1 from A to C递归不仅用于简单的数学问题,还广泛应用于数据结构和算法中。例如:
特性 | 递归 | 迭代 |
|---|---|---|
代码简洁性 | 通常更短 | 可能更复杂 |
性能 | 开销较大,可能栈溢出 | 通常更高效 |
使用场景 | 适合分治和树结构问题 | 适合简单循环问题 |
一、尾递归优化
尾递归的概念
int fibonacci_tail(int n, int a, int b) {
if (n == 0) {
return a;
} else {
return fibonacci_tail(n - 1, b, a + b);
}
}
int fibonacci(int n) {
return fibonacci_tail(n, 0, 1);
}fibonacci_tail函数中,递归调用fibonacci_tail(n - 1, b, a + b)是函数体中最后执行的操作,它直接返回递归调用的结果,没有其他后续计算。尾递归的优化原理
与普通递归的对比
int fibonacci_normal(int n) {
if (n == 0 || n == 1) {
return n;
} else {
return fibonacci_normal(n - 1) + fibonacci_normal(n - 2);
}
}fibonacci_normal(5)时,fibonacci_normal(3)会被多次计算。而尾递归版本通过参数传递避免了这种重复计算,并且在栈空间使用上更高效。二、动态规划优化
动态规划的概念
int fibonacci_dp(int n) {
if (n == 0 || n == 1) {
return n;
}
int dp[n + 1];
dp[0] = 0;
dp[1] = 1;
for (int i = 2; i <= n; i++) {
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp[n];
}dp来存储斐波那契数列的前n + 1个值,通过循环逐步计算出每个值,避免了递归中的重复计算。动态规划的优化原理
fibonacci(n)的计算依赖于fibonacci(n - 1)和fibonacci(n - 2),这就是重叠子问题。而最优子结构是指问题的最优解可以从其子问题的最优解构建而来。通过存储子问题的解,动态规划避免了重复计算子问题,从而提高了效率。与递归的对比
O(n),空间复杂度也可以通过一些技巧进一步优化到O(1)(如只存储最近的两个斐波那契数)。尾递归和动态规划都是优化递归的有效手段,在实际编程中,根据问题的特点选择合适的优化策略可以提高程序的性能和稳定性。