创作不易,欢迎 关注 点赞 收藏 留言
水平有限,不足之处,望友人指出
博主介绍:一枚不知所措的大学生
博客首页:Gredot
所属专栏《C语言》
目录
函数的递归:
1、递归的介绍
2、递归的必要条件
3、例题及练习:
栗子:
文解:
图解:
4、递归的不足
递归(recursion)是程序调用自身的编程技巧。递归算法在程序设计中的应用广泛,递归通常可以把一个大型复杂的问题层层转化为一个与原问题相似且规模较小的问题来求解。
执行递归函数将反复调用其自身,每调用一次就进入新的一层,当最内层的函数执行完毕后,再一层一层地由里到外退出。
递归算法只需要少量的程序就可以描述出解题过程所需的多次重复计算,大大减少了代码量。
①存在限制条件,满足这个条件时,递归结束;
②每次递归调用后越来越逼近限制条件。
#0 模拟实现库函数pow;pow函数是一个求x的n次方的函数,使用时应引math.h头文件。
#include
size_t MyPow(int x,int n)
{
if(0 == n)
return 1;
else
return x*MyPow(x,n-1);
}
int main()
{
int a = 0;
int b = 0;
scanf("%d%d",&a,&b);
int ret = MyPow(a,b);
printf("%d",ret);
return 0;
}
先看程序执行结果,那么递归算法在这个程序中时如何执行的呢?
调用MyPow()后进入函数体,只有当0==n成立时,函数调用才会完成,否则它就会一直调用自身。
由于每次调用的实参为(x,n-1),即把 n-1 的值赋给形参 n,所以每次递归实参的值都减 1,直到最后 n-1 的值为 1 时再作递归调用,形参 n 的值也为1,递归就终止了,会逐层退出。
求5的三次方:
递推(递归的进入):
①调用MyPow(5,3).当进入MyPow函数体后,由于由于形参 n 的值为 5,不等于0,所以执行 x*MyPow(x,n-1)即5*MyPow(5,2),为了得到5*MyPow(5,2)的值,必须调用MyPow(5,2),并暂停其他操作。换句话说,在得到 MyPow(5,2)的结果之前,不能进行其他操作。这就是第一次递推。
②调用 MyPow(5,2),实参为(5,2)n不为0,会继续执行 x*MyPow(x,n-1)即5*MyPow(5,1),为了得到5*MyPow(5,1)的值,又必须先调用MyPow(5,1),这就是第二次递推。
③调用 MyPow(5,1),实参为(5,1)n不为0,会继续执行 x*MyPow(x,n-1)即5*MyPow(5,0),为了得到5*MyPow(5,0)的值,又必须先调用MyPow(5,0),这就是第三次递推。
④调用MyPow(5,0),由于n==0,递推结束,返回1;
回归(递归的推出):
当递归进入到最内层的时候,递归就结束了,就开始逐层退出了,也就是逐层执行 return 语句。
①n 的值为 0 时达到最内层,此时 return 出去的结果为 1,也即MyPow(5,0) 的调用结果为 1。
②有了MyPow(5,0)的调用结果就可以返回上一层计算5*MyPow(5,0)=5,这是第一次回归。
③有了MyPow(5,1)的调用结果就可以返回上一层计算5*MyPow(5,1)=25,这是第二次回归。
④有了MyPow(5,2)的调用结果就可以返回上一层计算5*MyPow(5,2)=125,这是第三次回归。
至此,MyPow(5,3)的调用结束,返回结果125.
注:递归的时候,每次调用一个函数,计算机都会为这个函数分配新的空间,这就是说,当被调函数返回的时候,调用函数中的变量依然会保持原先的值,否则也不可能实现反向输出。
只要弄清楚算法的递推关系,递归也就不难理解了!
总结:
函数调用的时候,每次调用时要做地址保存,参数传递等,这是通过一个递归工作栈实现的。具体是每次调用函数本身要保存的内容包括:局部变量、形参、调用函数地址、返回值。
下面上几道练习题:
#1 使用递归求n!(不考虑溢出):
#include
size_t factorial(int x)
{
if(0 == x)
return 1;
else
return x*factorial(x-1);
}
int main()
{
int n = 0;
scanf("%d",&n);
long long ret = factorial(n);
printf("%ld",ret);
return 0;
}
#2 使用递归求第n个斐波那契数列(不考虑溢出):
#include
int fbi(int n)
{
int a = 1;
int b = 1;
int c = 1;
if(n==1||n==0)
return c;
else
return fbi(n - 1)+ fbi(n - 2);
}
int main()
{
int x = 0;
scanf("%d",&x);
printf("%d",fbi(x));
return 0;
}
#3 编写函数不允许创建临时变量,求字符串的长度
#incude
int Strlen(const char*str)
{
if(*str == '\0')
return 0;
else
return 1+Strlen(str+1);
}
int main()
{
char *p = "abcdef";
int len = Strlen(p);
printf("%d\n", len);
return 0;
}
递归在解决某些问题上确实短小精悍,但递归并不适用于所有问题。
当递归层次较深的时候,会占用较大的栈区空间,而系统分配的栈空间是有限的,当栈区空间耗尽时,会导致stack overflow(栈溢出);
如何解决呢?
1.使用迭代(循环)的方式替换递归。
2.使用static对象替代非static对象,从而减少每次函数调用对栈区空间的消耗;
最后:
1. 但是有些问题的迭代实现往往比递归实现效率更高,虽然代码的可读性稍微差些。
2. 当一个问题相当复杂,难以用迭代实现时,此时递归实现的简洁性便可以补偿它所 带来的运行时开销。
最后的最后:
这是Gredot的第一篇博客,仅用于记录Gredot学习编程的过程,分享知识,由于水平有限,文中不足望各位大佬指出、指点。
ps:附上哈尔滨的晚霞,向你问好!