什么是函数递归?
程序调用自身的编程技巧称为递归( recursion)。 递归做为一种算法在程序设计语言中广泛应用。一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。
递归的主要思考方式在于:把大事化小
函数递归的必要条件
- 存在限制条件,当满足这个限制条件的时候,递归便不再继续。
- 每次递归调用之后越来越接近这个限制条件。
关于无限递归下去所出现的问题
每一次调用函数时都会在内存中(栈区)开辟空间,当调用还没有返回时,原先开辟的空间依然存在,所以无限调用一直不返回,栈区的内存一直被占用,当超出内存空间时就会栈溢出。
函数栈帧的越出简洁图如下:
递归例题,理清递归的逻辑
- 接受一个整型值(无符号),按照顺序打印它的每一位。 例如: 输入:1234,输出 1 2 3 4.
逻辑分解图:
实现过程:
#include<stdio.h>
void Print(unsigned int n)
{
if(n>9)
{
Print(n/10);
}
printf("%d",n%10);
}
int main()
{
unsigned int num=0;
scanf("%d",&num);
Print(num);
}
递归过程分解:
- 编写函数不允许创建临时变量,求字符串长度
逻辑分解图:
实现过程:
int My_strlen(char *parr)
{
if(*parr!='\0')
{
//注意这里不能parr++,后置++传过去的不是下一个元素的地址
return 1+My_strlen(parr+1);
}
else
{
return 0;
}
}
#include<stdio.h>
int main()
{
char arr[10]="abc";
int ret=0;
ret=My_strlen(arr);
return 0;
}
递归分解过程:
递归与迭代(循环)
- 求n的阶乘
int Fac(int n)//迭代
{
int i=0;
int ret=1;
for(i=1;i<=n;i++)
{
ret*=i;
}
return ret;
}
int Fac(int n)//递归
{
if(n<=1)
return 1;
else
return n*Fac(n-1);
}
- 但是一些问题不适合用递归与迭代的方法进行解决,如求第n个斐波那契数列数
什么是斐波那契数列?
代码实现:
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);
}
-
注意:
1、在使用 Fib 这个函数的时候如果我们要计算第50个斐波那契数字的时候特别耗费时间。2、使用 factorial 函数求10000的阶乘(不考虑结果的正确性),程序会崩溃。
-
为什么会出现这样的原因?
如果要求第50个,会将50拆成48和49;而48又拆成46和47,49拆成47和48,就这样依次进行下去,需要大量的时间冗余和内存消耗,所以会出现上述的原因。 -
解决方法:
1、将递归改写成非递归。
2、使用static对象替代nonstatic局部对象。在递归函数设计中,可以使用static对象替代nonstatic局部对象(即栈对象),这不仅可以减少每次递归调用和返回时产生和释放nonstatic对象的开销,而且static对象还可以保存递归调用的中间状态,并且可为各个调用层所访问。
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;
}
什么时候使用递归?
1、当解决一个问题递归和非递归都可以使用,且没有明显问题,那就可以使用递归
2、当解决一个问题递归写起来很简单,非递归比较复杂,且递归没有明显问题,那就用递归。
3、如果说用递归解决问题写起来简单,但是有明显问题,那就不能使用。
递归注意的事项
1、许多问题是以递归的形式进行解释的,这只是因为它比非递归的形式更为清晰。
2、但是这些问题的迭代实现往往比递归实现效率更高,虽然代码的可读性稍微差些。
3、当一个问题相当复杂,难以用迭代实现时,此时递归实现的简洁性便可以补偿它所带来的运行时开销。