1. 递归是什么?
递归是一种在函数中调用自身的编程技巧即函数自己调用自己
写一个史上最简单的递归:
#include <stdio.h>
int main()
{
printf("hehe\n");
main();//自己调用自己
return 0;
}
1.1 递归的思想
递归的基本思想是:一个问题的解决可以通过将其分解成相同类型的子问题来实现。每个子问题都会进一步分解,直到遇到“基准情况”——即不再需要递归的情况
递归的思考方式就是把大事化小的过程
递归中的递就是递推的意思,归就是回归的意思
1.2 递归的限制条件
递归函数必须有以下两个条件:
1.递归存在限制条件,当满足这个限制条件的时候,递归便不再继续
2.每次递归调用之后越来越接近这个限制条件
2. 递归举例
2.1 举例1: 求 n 的阶乘
阶乘是一个经典的递归问题,定义为:n! = n * (n - 1)!
,当 n = 0
时,0! = 1
2.1.1 分析和代码实现
阶乘的递归定义为:
#include <stdio.h>
Fact(int n)
{
if (0 == n)
return 1;
else
return n * Fact(n - 1);
}
int main()
{
int n = 0;
scanf("%d", &n);
int a = Fact(n);
printf("%d\n", a);
return 0;
}
2.1.2 画图推演
以 Fact(5)
为例:
每一次递归调用都在等待下一次递归的返回值,直到遇到基准情况 Fact(0)
,然后逐步返回结果
在c语言中,每一次函数调用,都要为这次函数调用
在栈区申请一块内存空间,用来为这次函数调用存放信息
这一块空间就叫:运行时堆栈 或者 函数栈帧空间
2.2 举例2: 顺序打印一个整数的每一位
我们可以通过递归来顺序打印整数的每一位
首先,递归将整数的最后一位打印出来,然后再递归处理剩余部分
2.2.1 分析和代码实现
例如,打印 1234
:
void Set(int n)
{
if (n > 9)
Set(n / 10);
printf("%d ", n % 10);
}
#include <stdio.h>
int main()
{
int n = 0;
scanf("%d", &n);
Set(n);
return 0;
}
2.2.2 画图推演
以 Set(1234)
为例:
Set(1234)
Set(123)+ 4
Set(12)+ 3 + 4
Set(1)+ 2 + 3 + 4
递归逐步处理每一位,直到所有位都被打印出来
3. 递归与迭代
递归和迭代(循环)都可以解决相同的问题,但它们的实现方式不同。递归通过函数调用来重复执行,迭代通过循环来完成相同的任务。
举例3: 求第 n 个斐波那契数
斐波那契数列的定义为:
Fib(n) = 1 (n <= 2)
Fib(n) = F(n-1) + F(n-2) (n > 2)
递归实现
int count = 0;//查看计算量
int Fib(int n)
{
if (n == 3)
count++;
if (n <= 2)
return 1;
else
return Fib(n - 1) + Fib(n - 2);
}
#include <stdio.h>
int main()
{
int n = 0;
scanf("%d", &n);
int r = Fib(n);
printf("%d\n", r);
printf("count = %d", count);
return 0;
}
迭代实现
int Fib(int n)
{
int a = 1;
int b = 1;
int c = 0;
if (n <= 2)
return 1;
while (n > 2)
{
c = a + b;
a = b;
b = c;
n--;
}
return c;
}
#include <stdio.h>
int main()
{
int n = 0;
scanf("%d", &n);
int r = Fib(n);
printf("%d\n", r);
return 0;
}
递归 vs 迭代
- 递归:代码简洁,但在处理较大数据时可能导致栈溢出
- 迭代:通常更高效,不易出现栈溢出问题
递归的定义
递归是指在解决一个问题时,函数调用自身来解决子问题,直到满足某个终止条件为止。递归通常包含两个关键部分:
基本情况(Base Case):递归的终止条件
递归步骤(Recursive Case):函数调用自身来解决更小的子问题
迭代的定义
迭代是指通过循环(如for循环或while循环)反复执行某些操作,直到满足某个条件为止。每次循环通过改变某些变量来推进计算,直到达成终止条件
递归和迭代的区别
- 思想和执行方式:
- 递归是通过函数自身调用来解决问题,常常涉及到堆栈的使用来保持每次函数调用的状态
- 迭代是通过循环结构在内存中逐步计算,每次循环更新变量的状态
- 内存使用:
- 递归通常需要更多的内存,因为每次函数调用都需要在栈上分配新的内存空间(递归深度大时可能会导致栈溢出)
- 迭代通常只需要固定的内存空间来存储当前的状态
- 代码结构:
- 递归的代码结构通常较简洁,能够更自然地表达一些分治类问题(如树的遍历)
- 迭代代码结构通常更长,尤其是在需要手动维护循环变量时,但它避免了递归可能带来的堆栈溢出问题
- 性能:
- **递归由于函数调用的开销,通常会比迭代慢,**尤其在深度较大的情况下
- 迭代通常更高效,尤其是对于简单的循环问题