递归与迭代
- 1.什么是递归?
- 2.递归的限制条件
- 3.递归举例
- 3.1 求n的阶乘
- 3.2 顺序打印一个整数的每一位
- 4.递归与迭代
- 4.1 求第n个斐波那契数(递归 不推荐)
- 4.2 求第n个斐波那契数(迭代 推荐)
- 4.3 总结
1.什么是递归?
递归是一种解决问题的方法,递归就是函数自己调用自己。
例:
int main() {
printf("调用函数main");
main();
return 0;
}
上述代码只是为了演示递归的形式,并非解决问题:
main函数中调用main函数,代码最终陷入死递归,无限调用main函数,最终导致栈溢出(overflow)
输出结果卡死,报错栈溢出
递归的思想:
把一个大型复杂的问题转化为一个与原问题相似,但是规模较小的子问题来求解,直到这个大型问题被拆分,递归就结束了。即大事化小的思路。
2.递归的限制条件
递归在书写格式上有2个必要条件:
1.递归存在限制条件,当满足这个限制条件的时候,递归便不再继续,停止。
2.每次递归调用之后越来越接近这个限制条件
以下举例几个递归的题目,来感受一下
3.递归举例
3.1 求n的阶乘
计算n的阶乘(不考虑溢出),n的阶乘就是1~n的数字累积相乘。
n的阶乘的公式:n! = n ∗ (n − 1)!
1.如果n等于0,则直接返回1,因为0的阶乘定义为1
2.否则,将n与(n-1)的阶乘结果相乘,并返回这个乘积
3.直至fact(n)中的n被减至0,跳出递归
//求n的阶乘
//计算n的阶乘(不考虑溢出),n的阶乘就是1~n的数字累积相乘。
int fact(int n) {
if (n = 0) {//如果n等于0,则直接返回1,因为0的阶乘定义为1。
return 1;
}
else//否则,将n与(n-1)的阶乘结果相乘,并返回这个乘积
{
//这里就是把一个大的数拆成了一个小的数乘上一个递归函数
//直至fact(n)中的n被减至0,跳出递归
return n * fact(n - 1);
}
return 0;
}
int main() {
int n;
scanf("%d", &n);
printf("%d!= %d",n,fact(n));
return 0;
}
3.2 顺序打印一个整数的每一位
输⼊⼀个整数n,打印这个按照顺序打印整数的每⼀位
输⼊:1234 输出:1 2 3 4
输⼊:520 输出:5 2 0
n % 10 取最后一位
n / 10 去掉最后一位
void Print_Frist(int n) {
if (n > 9) {
//这里先递归再去打印最后一位
Print_Frist(n / 10);
}
//打印最后一位
printf("%d ", n % 10);
}
int main() {
int n;
scanf("%d", &n);
Print_Frist(n);
return 0;
}
错误的思路:
void Print_Frist(int n) {
if (n > 9) {
//如果这里把n除以10,那么此次函数中的打印最后一位就会出问题
n = n / 10;
Print_Frist(n);
}
//打印最后一位,这里会出问题
printf("%d ", n % 10);
}
原因:
例输入1234,if语句 中如果 n = n / 10
再去将n当作Print_Frist(n)函数的参数(这里确实没问题);但是,此次函数printf("%d “, n % 10);中的n也将随之改变,原先是4,n = n / 10之后变成3
函数递归调用时,此次函数尚未完全结束,即调用至Print_Frist(n)时,下面的printf(”%d ", n % 10)尚未执行
我们需要的只是将循环调用函数中的 参数/10 ,而本次函数中的n仍然不变
4.递归与迭代
强调一下,下列题目中,斐波那契数函数中 int Fibonacci(int n) 中的n仅仅是函数中的参数,从0开始;而参数为0的,为第一项,以此类推。(所以打印结果的时候会+1)
F(0) = 0 //第一项
F(1) = 1 //第二项
4.1 求第n个斐波那契数(递归 不推荐)
斐波那契数列是一个经典的数列,在数学上以如下递归关系定义:
F(0) = 0
F(1) = 1
F(n) = F(n-1) + F(n-2)
换句话说,斐波那契数列的前两项是0和1,之后的每一项都是前两项的和。
//递归方法
int count = 0;//调用函数次数
int Fibonacci(int n) {
if (n <= 1) {
count++;
return n;
}
else{
count++;
return Fibonacci(n-1) + Fibonacci(n-2);
}
}
int main() {
int n;
scanf("%d", &n);
printf("第%d斐波那契数 = %d\n", n + 1, Fibonacci(n));
printf("共调用了函数 %d 次\n",count);
return 0;
}
不合适
这样计算确实可以得到正确的结果,但是这个函数一共调用了接近4千万次
C语言中每次调用函数都需要在栈区申请一块内存空间来保存函数调用期间的各种局部变量的值,这块空间被称为运行时堆栈,或者函数栈帧。
上述斐波那契数中 return Fibonacci(n - 1) + Fibonacci(n - 2) 这一步,在返回值这里再次递归调用函数,函数都尚未结束,空间都还没释放,就要再递归开辟一个新的内存空间,使得每一次递归都开辟一个新的栈帧空间,直至return开始回归,才会逐层释放栈帧空间。
4.2 求第n个斐波那契数(迭代 推荐)
迭代是一种重复执行某个操作或过程的方法。在编程中,迭代通常用于处理集合(如列表、数组等)中的每个元素,或者重复执行某个代码块直到满足特定条件。(顾名思义,就是更新迭代,把变量换成另一个新的变量)
//迭代
int count = 0;
int Fibonacci(int n) {
if (n <= 1) {
count++;
return n;
}
int a = 0; // 第 0 项
int b = 1; // 第 1 项
int fib = 0; // 第 n 项
for (int i = 2; i <= n; i++) {
fib = a + b;
//相当于都往后走了一位,因为这三个数是连续的
a = b;
b = fib;
count++;
}
return fib;
}
int main() {
int n;
scanf("%d", &n);
printf("第%d个斐波那契数 = %d\n",n+1, Fibonacci(n));
printf("共执行了 %d 次\n",count);
return 0;
}
上述代码使用了迭代效率相比于递归效率高出实在太多了,
4.3 总结
递归虽然是一种非常好的思路和想法,但是不能一昧的钻进递归中,任何方法都有自身的优点和缺点,所以具体问题具体分析,一种问题有多种解决方案,优先选择效率高的一种。