1.什么是递归
递归其实是⼀种解决问题的⽅法,在C语⾔中,递归就是函数⾃⼰调⽤⾃⼰。
写⼀个史上最简单的C语⾔递归代码:
上述就是⼀个简单的递归程序,只不过上⾯的递归只是为了演⽰递归的基本形式,不是为了解决问 题,代码最终也会陷⼊死递归,导致栈溢出(Stackoverflow)。
调试一下:
栈溢出:
每一次函数调用都会占有一块内存空间:函数栈帧(运行时堆栈),其是在栈区上申请的空间。当其放满时,就溢出了。
所以,函数是不可以这样无限制递归下去的,递归必须要有条件。
2.递归的思想
把⼀个⼤型复杂问题层层转化为⼀个与原问题相似,但规模较⼩的⼦问题来求解;直到⼦问题不能再 被拆分,递归就结束了。
所以递归的思考⽅式就是把⼤事化⼩的过程。
递归中的递就是递推的意思,归就是回归的意思,接下来慢慢来体会。
3.递归的限制条件
1.递归存在限制条件,当满⾜这个限制条件的时候,递归便不再继续。
2.每次递归调⽤之后越来越接近这个限制条件。
4.递归举例1:求n的阶乘
⼀个正整数的阶乘(factorial)是所有⼩于及等于该数的正整数的积,并且0的阶乘为1。 ⾃然数n的阶乘写作n!。
题⽬:计算n的阶乘(不考虑溢出),n的阶乘就是1~n的数字累积相乘。
分析和代码实现:
我们知道n的阶乘的公式: n ! = n∗(n−1)!
这样的思路就是把⼀个较⼤的问题,转换为⼀个与原问题相似,但规模较⼩的问题来求解的。
当 n==0 的时候,n的阶乘是1(数学定义),其余n的阶乘都是可以通过公式计算。
n的阶乘的递归公式如下:
过程:
其中:
1.递归条件:n>0,递归停止条件:n=0,
2.不断的逼近跳出条件
5.递归举例2:顺序打印⼀个整数的每⼀位
输⼊⼀个整数m,打印这个按照顺序打印整数的每⼀位。
⽐如:
输⼊:1234
输出:1 2 3 4
分析和代码实现
这个题⽬,放在我们⾯前,⾸先想到的是,怎么得到这个数的每⼀位呢?
如果n是⼀位数,n的每⼀位就是n⾃⼰
n是超过1位数的话,就得拆分每⼀位
1234%10就能得到4
然后1234/10得到123
这就相当于去掉了4 然后继续对123%10,就得到了3
以此类推不断的 %10 和 /10 操作,直到1234的每⼀位都得到;但是这⾥有个问题就是得到的数字顺序是倒着的。
我们假设想写⼀个函数Print来打印n的每⼀位,如下表⽰:
以此类推:
直到被打印的数字变成⼀位数的时候,就不需要再拆分,递归结束。
在这个解题的过程中,我们就是使⽤了⼤事化⼩的思路
把Print(1234) 打印1234每⼀位,拆解为⾸先Print(123)打印123的每⼀位,再打印得到的4
把Print(123) 打印123每⼀位,拆解为⾸先Print(12)打印12的每⼀位,再打印得到的3
直到Print打印的是⼀位数,直接打印就⾏
6.递归与迭代
递归是⼀种很好的编程技巧,但是很多技巧⼀样,也是可能被误⽤的,就像举例1⼀样,看到推导的公 式,很容易就被写成递归的形式。
Fact函数是可以产⽣正确的结果,但是在递归函数调⽤的过程中涉及⼀些运⾏时的开销。
在C语⾔中每⼀次函数调⽤,都要需要为本次函数调⽤在栈区申请⼀块内存空间来保存函数调⽤期间 的各种局部变量的值,这块空间被称为运⾏时堆栈,或者函数栈帧。
函数不返回,函数对应的栈帧空间就⼀直占⽤,所以如果函数调⽤中存在递归调⽤的话,每⼀次递归 函数调⽤都会开辟属于⾃⼰的栈帧空间,直到函数递归不再继续,开始回归,才逐层释放栈帧空间。
所以如果采⽤函数递归的⽅式完成代码,递归层次太深,就会浪费太多的栈帧空间,也可能引起栈溢 出(stackoverflow)的问题。
所以如果不想使⽤递归就得想其他的办法,通常就是迭代的⽅式(通常就是循环的⽅式)。 ⽐如:计算n的阶乘,也是可以产⽣1~n的数字累计乘在⼀起的。
事实上,我们看到的许多问题是以递归的形式进⾏解释的,这只是因为它⽐⾮递归的形式更加清晰, 但是这些问题的迭代实现往往⽐递归实现效率更⾼。
当⼀个问题⾮常复杂,难以使⽤迭代的⽅式实现时,此时递归实现的简洁性便可以补偿它所带来的运 ⾏时开销。
(有时候,一个代码,虽然递归非常不好想,但是,只要递归想到了,用递归写代码就会非常简单,就几行就写完了,但是如果是非递归,就要写好多行)
但是如果递归的不恰当书写,就会导致不可估量的后果,此时我们还是要放弃递归。
7.举例3:求第n个斐波那契数
斐波那契数列:
1 1 2 3 5 8 13 21 34 ……
第n个斐波那契数:
看到这公式,很容易诱导我们将代码写成递归的形式,如下所⽰:
当我们n输⼊为50的时候,需要很⻓时间才能算出结果,这个计算所花费的时间,是我们很难接受的, 这也说明递归的写法是⾮常低效的,那是为什么呢?
首先,我们通过以下代码,计算一下第40个斐波那契数在计算时,对第三个斐波那契数重复的次数:
所以,对于第50个斐波那契数计算时进行分析:
其中有大量的重复计算,⽽且递归层次越深,冗余计算就会越多。
修改一下,会发现代码运行速度特别快:
本篇文章中所涉及到的代码如下所示:
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
计算阶乘
//int Fact(int n)
//{
// if (n == 0)
// return 1;
// else//n>0
// return n * Fact(n - 1);
//}
//int main()
//{
// int n = 0;
// scanf("%d", &n);//4 24 //5 120
// int ret = Fact(n);
// printf("%d\n", ret);
// return 0;
//}
顺序打印⼀个整数的每⼀位
//void Print(int n)//1234
//{
// if (n > 9)
// {
// Print(n / 10);//123
// //printf("%d ", n % 10);//4
// }
// /*else
// {
// printf("%d ", n % 10);
// }*/
// printf("%d ", n % 10);
//}
//int main()
//{
// int n = 0;
// scanf("%d", &n);
// Print(n);
// return 0;
//}
换成迭代的方法
//int Fact(int n)
//{
// int i = 0;
// int ret = 1;
// for (i = 1; i <= n; i++)
// {
// ret *= i;
// }
// return ret;
//}
//int main()
//{
// int n = 0;
// scanf("%d", &n);
// int ret = Fact(n);
// printf("%d\n", ret);
// return 0;
//}
斐波那契数列
//int Fib(int n)
//{
// if (n < 2)
// return 1;
// else
// return Fib(n - 1) + Fib(n - 2);
//}
//int main()
//{
// int n = 0;
// scanf("%d", &n);
// int r = Fib(n);
// printf("%d\n", r);
// return 0;
//}
计算次数
//int count = 0;
//int Fib(int n)
//{
// if (n == 3)
// count++;
// if (n <= 2)
// return 1;
// else
// return Fib(n - 1) + Fib(n - 2);
//}
//int main()
//{
// int n = 0;
// scanf("%d", &n);
// int r = Fib(n);
// printf("%d\n", r);
// printf("count = %d\n", count);
// return 0;
//}
//用迭代的方式进行斐波那契数的计算
int Fib(int n)
{
int a = 1;
int b = 1;
int c = 0;
while (n >= 3)
{
c = a + b;
a = b;
b = c;
n--;
}
return c;
}
int main()
{
int n = 0;
scanf("%d", &n);
int r = Fib(n);
printf("%d\n", r);
return 0;
}
(学习内容无偿分享,如果觉得有用的话,麻烦各位大佬三连!就是最大的支持啦!)