函数递归
什么是递归?
程序调用自身的编程技巧称为递归。递归作为一种算法在程序设计语言中广泛应用。一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题求解,递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。递归的主要思考方式在于:把大事化小
递归的两个必要条件
- 存在限制条件,当满足这个限制条件的时候,递归便不再继续。
- 每次递归调用之后越来越接近这个限制条件。
递归练习:
接收一个整型值(无符号),按照顺序打印它的每一位。例如:输入1234,输出1 2 3 4
void printf(unsigned int n)
{
if( n > 9)
{
printf(n / 10);
}
printf("%d",n %10);
}
int mian()
{
unsigned int a =0;
scanf("%u",&a); //1234
//递归 - 函数自己调用自己
printf(a); //printf函数可以打印参数部分数字的每一位
return 0;
}
Console:
1234
1 2 3 4
一个简单的递归
int mian()
{
printf("hello\n");
main();
return 0;
}
这段代码运行起来会死循环,然后停止,报一个栈溢出的错误
void test(int n)
{
if( n < 10000)
{
test(n + 1);
}
}
int mian()
{
int a = 10;
test(1);
return 0;
}
同样的,这段代码也会造成栈溢出的情况,那到底什么是栈溢出?
内存中的栈区,是有限度的,每一次递归的调用,并没有销毁前一个调用的函数,也就是说前一个函数并没有消失,而是留在了栈区中,这样久而久之,越积越多,栈就会爆满,甚至溢出
_____________
| test |
| test |
| test |
| test |
|main的栈帧空间|
|_____________|
所以我们在写递归代码的时候要注意:
- 不能死递归,都有跳出条件,每次递归逼近跳出条件
- 递归层次不能太深(导致栈溢出)
递归练习2:
编写函数不允许创建临时变量,求字符串长度
int strlen(char* str)
{
if(*str != '\0')
return 1 + strlen(str + 1); //str+1是下一个字符的地址
else
return 0;
}
int mian()
{
char arr[] = "hello";
printf("%d\n",strlen(arr));
return 0;
}
Console:
4
递归与迭代
练习3:
求n的阶乘。(不考虑溢出)
迭代做法:
int mian()
{
int a =5;
scanf("%d",&a);
int i =0;
int ret = 1;
//迭代
for( i = 1; i <= a; i++)
{
ret = ret * i;
}
printf("%d\n",ret);
return 0;
}
递归做法:
int Fac(int n)
{
if(n <= 1)
return 1;
else
return n * Fac(n -1);
}
int mian()
{
int n =0;
scanf("%d",&n);
int ret = Fac(n);
printf("%d\n",ret);
return 0;
}
从上面的例子可以知道,有一些功能:可以使用迭代的方式实现,也可以使用递归实现
练习4:
求第n个斐波那契数。(不考虑溢出)
递归求解:效率低
int fib(int x)
{
if(x <= 2)
return 1;
else
return fib(n-1) + fib(n-2);
}
int main()
{
int x = 0;
scnaf("%d",&x);
int ret = fib(n);
printf("%d\n",);
return 0;
}
使用迭代来求解:
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;
}
int main()
{
int n = 0;
scanf("%d",&n);
int ret = fib(n);
printf("%d\n",ret);
return 0;
}