一、前言
最近学了一下C语言函数递归的内容感觉挺有趣的,这也不为是一种非常奇妙的解决问题的方法,那现在我就来介绍一下函数的递归,如果觉得写得不错,一定不要忘了点赞、收藏,你的支持就是我更新的动力,万分感谢!!
二、递归知识的讲解
2.1什么是递归?
递归是学习C语⾔函数绕不开的⼀个话题,那什么是递归呢? 递归其实是⼀种解决问题的⽅法,在C语⾔中,递归就是函数⾃⼰调⽤⾃⼰。 写⼀个十分简单的C语⾔递归代码:
#include <stdio.h>
int main()
{
printf("hehe\n");
main();//main函数中⼜调⽤了main函数
return 0;
}
上述就是⼀个简单的递归程序,只不过上⾯的递归只是为了演⽰递归的基本形式,不是为了解决问题,代码最终也会陷⼊死递归,导致栈溢出(Stackoverflow)。
递归的思想: 把⼀个⼤型复杂问题层层转化为⼀个与原问题相似,但规模较⼩的⼦问题来求解;直到⼦问题不能再 被拆分,递归就结束了。所以递归的思考⽅式就是把⼤事化⼩的过程。 递归中的递就是递推的意思,归就是回归的意思,接下来慢慢来体会!!
2.2函数递归的限制条件
递归在书写的时候,有2个必要条件:
- 递归存在限制条件,当满⾜这个限制条件的时候,递归便不再继续。
- 每次递归调⽤之后越来越接近这个限制条件。
在下⾯的例⼦中,我们逐步体会这2个限制条件。
2.3递归的使用例子
1.求n的阶乘
计算n的阶乘(不考虑溢出),n的阶乘就是1~n的数字累积相乘。
我们知道n的阶乘的公式: n ! = n∗(n−1)!
5! = 5*4*3*2*1
4! = 4*3*2*1
所以 :5! = 5*4!
这样的思路就是把⼀个较⼤的问题,转换为⼀个与原问题相似,但规模较⼩的问题来求解的。
再就是要注意,n<=1时 ,n!=1。
那我们就可以写出函数Fact求n的阶乘,假设Fact(n)就是求n的阶乘,那么Fact(n-1)就是求n-1的阶 乘,函数如下:
int Fact(int n)
{
if(n<=0)
return 1;
else
return n*Fact(n-1);
}
接下来我们来测验一下:
#include <stdio.h>
int Fact(int n)
{
if(n<=0)
return 1;
else
return n*Fact(n-1);
}
int main()
{
int n = 0;
scanf("%d", &n);
int ret = Fact(n);
printf("%d\n", ret);
return 0;
}
我们可以通过画图来推演一下函数递归的全过程:
大家看明白了吗?就算没明白也没关系,我们再来看一个例子:
2.按顺序打印数的每一位
输⼊⼀个整数m,打印这个按照顺序打印整数的每⼀位。
⽐如: 输⼊:1234
输出:1 2 3 4
输⼊:520
输出:5 2 0
这样的题我们因该怎么分析呢?
如果n是⼀位数,n的每⼀位就是n⾃⼰ n是超过1位数的话,就得拆分每⼀位 1234%10就能得到4,然后1234/10得到123,这就相当于去掉了4 然后继续对123%10,就得到了3,再除10去掉3,以此类推 不断的 %10 和 \10 操作,直到1234的每⼀位都得到; 但是这⾥有个问题就是得到的数字顺序是倒着的
但是我们也有了灵感,一个数的各位是最容易等到的,通过%10就能够轻松的得到,
那我们不妨设想一个Print函数打印n的每一位,如下:
Print(n) 如果 n 是 1234 ,那表⽰为
Print(1234) // 打印 1234 的每⼀位
其中 1234 中的 4 可以通过 %10 得到,那么
Print(1234) 就可以拆分为两步:
1. Print(1234/10) // 打印 123 的每⼀位
2. printf(1234%10) // 打印 4
完成上述 2 步,那就完成了 1234 每⼀位的打印
那么 Print(123) ⼜可以拆分为 Print(123/10) + printf(123%10)
以此类推:
Print(1234)==>Print(123) +printf(4)
==>Print(12) + printf(3)
==>Print(1) + printf(2)
==>printf(1)
直到被打印的数字变成⼀位数的时候,就不需要再拆分,递归结束。 那么代码完成也就⽐较清楚:
void Print(int n)
{
if(n>9)
{
Print(n/10);
}
printf("%d ", n%10);
}
我们再来检验一下:
void Print(int n)
{
if(n>9)
{
Print(n/10);
}
printf("%d ", n%10);
}
int main()
{
int m = 0;
scanf("%d", &m);
Print(m);
return 0;
}
我们再来画图推演一下:
三、递归与迭代
递归确实是一种很好的编程技巧,但亦有可能被误用
在C语⾔中每⼀次函数调⽤,都要需要为本次函数调⽤在栈区申请⼀块内存空间来保存函数调⽤期间 的各种局部变量的值,这块空间被称为运⾏时堆栈,或者函数栈帧。 函数不返回,函数对应的栈帧空间就⼀直占⽤,所以如果函数调⽤中存在递归调⽤的话,每⼀次递归 函数调⽤都会开辟属于⾃⼰的栈帧空间,直到函数递归不再继续,开始回归,才逐层释放栈帧空间。 所以如果采⽤函数递归的⽅式完成代码,递归层次太深,就会浪费太多的栈帧空间,也可能引起栈溢出(stackoverflow)的问题。
如果不用递归那就得想想其他的方法了,通常就是迭代的方法了(通常就是循环的方式)
比如计算n的阶乘,就可以产生1--n的书数值累积乘在一起:
int Fact(int n)
{
int i = 0;
int ret = 1;
for(i=1; i<=n; i++)
{
ret *= i;
}
return ret;
}
上面的代码就能完成任务,而且比递归的方式更好!!
事实上,我们看到的许多问题是以递归的形式进⾏解释的,这只是因为它⽐⾮递归的形式更加清晰, 但是这些问题的迭代实现往往⽐递归实现效率更⾼。 当⼀个问题⾮常复杂,难以使⽤迭代的⽅式实现时,此时递归实现的简洁性便可以补偿它所带来的运⾏时开销。
3.求第n个斐波那契数
斐波那契数列:1 1 2 3 5 8~~
从第3为开始,前两个数的和就是数列下一个数的值
看到这公式,很容易诱导我们将代码写成递归的形式,如下所⽰:
int Fib(int n)
{
if(n<=2)
return 1;
else
return Fib(n-1)+Fib(n-2);
}
测试代码:
#include <stdio.h>
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 ret = Fib(n);
printf("%d\n", ret);
return 0;
}
我们可以发现当我们输入50时,需要花很长的时间才能计算出结果,这也说明了,用递归这个方法来解决这一问题十分的低效,但这又是为什么呢?
有图我们可知,随着程序的展开,递归程序中会出现许多的重复运算,递归的层次越深,冗余的计算越多,效率就越低下。
那这时我们就可以用迭代的思虑来解决问题了 。
我们知道斐波那契数的前2个数都1,然后前2个数相加就是第3个数,那么我们从前往后,从⼩到⼤计算就⾏了。 这样就有下⾯的代码:
int Fib(int n)
{
int a = 1;
int b = 1;
int c = 1;
while(n>2)
{
c = a+b;
a = b;
b = c;
n--;
}
return c;
}
这样效率就大多了!!
结语
好了今天的函数递归与迭代问题就介绍到这里,感谢大家的支持,如果有学习上的问题欢迎大家来评论区友好讨论哦,咱们下次再见!!