在讲函数递归之前我们要知道什么是函数递归?
函数递归就是自己调用自己。这里我们要把递归这个词分开理解,递归即先递推后回归,一定要记住这个原理。
1. 递归的思想
递归的总的思想就是把一个大的问题逐步转化为一个与原问题相似,但相对于之前的问题更小的情况来求解,直到最终问题不能在被拆解为更小的问题,递推就结束了,然后在回归到原问题。所以递归的思考方式是把问题大化小的过程·。
2. 递归的限制条件
递归在书写的时候,有2个必要的条件:
递归存在的限制条件,当满足这个限制的时候,递归的递推结束。
每次递归调用之后越来越接近这个限制条件。
如果我们不给递归加上限制条件,那么将会导致程序进入死循环,程序栈溢出。
3. 递归举例
3.1 例1:求n的阶乘
计算N的阶乘(不考虑溢出),n的阶乘就是1~n的数字累积相乘。
思路分析:
假如我们求4的阶乘,那么它就是4乘以3的阶乘,那么3的阶乘不就是3乘以2的阶乘么?
4! = 4 * 3!
3! = 3 * 2!
2! = 2 * 1
上面的的显示我们分析出n的阶乘就是n * (n-1)!,(n-1)!的阶乘就是(n-1) * (n-2)!。直到n为1或者0时,阶乘结束。
我们用代码演示下:
#include <stdio.h>
int func(int n)
{
if (n == 1)
{
return 1;
}
else
{
return n * func(n - 1);
}
}
我们测试下:
#include <stdio.h>
int func(int n)
{
if (n == 1)
{
return 1;
}
else
{
return n * func(n - 1);
}
}
int main()
{
int x = 0;
scanf("%d", &x);
int ret = func(x);
printf("%d", ret);
return 0;
}
运行结果:
3.1.2 画图演示
3.2 例2:顺序打印一个整数的每一位
输入一个整数m,顺序打印这个整数的每一位。
比如:
输入:1234 输出:1 2 3 4
输入:520 输出:5 2 0
分析:
我们知道当输入一个十进制整数时,如果要得到这个数的个位数,那么我们就用这个数求余10。如果要得到十位数的数,就把这个数字除以10,然后再求余,即得到十位数的数字了。但是这种情况下输出的是 反序的。
我们用递归来实现的话,就是把这个数值除以10得到的数字传递给函数,直到最后传递进去的数值小于9时回归,执行下面的语句。为什么是大于9,因为当商为小于9的数值,那么就是到了最后一位数了。
#include <stdio.h>
void func(int n)
{
if (n > 9)
{
func(n / 10);
}
printf("%d ", n%10);
}
int main()
{
int x = 0;
scanf("%d", &x);
func(x);
return 0;
}
输出结果:
3.2.1 画图演示
4. 递归与迭代
我们通常说的迭代,通常情况下指的就是循环,递归虽然是一种很好的编程技巧,但是也有被误用的情况,比如例1,知道递归的原理我们很容易就能写出来递归形式的代码,当然我们用循环也能实现,但是递归相对于循环,代码更简洁一些。
这里我们要注意,如果递归使用不当,将会使程序进入大量的计算中,此时使用迭代会是一种很好的解决办法。比如斐波那契数列的实现,虽然用递归可以实现,但是计算机要经过大量的计算才能得出数值,而迭代的方式相比较起来会更简单。
递归实现斐波那契数列
#include <stdio.h>
int feb(int x)
{
if (x <= 2)
{
return 1;
}
else
{
return feb(x - 1) + feb(x - 2);
}
}
int main()
{
int a = 0;
scanf("%d", &a);
int ret = feb(a);
printf("%d", ret);
return 0;
}
此时我们用递归实现斐波那契数列,如果求小的值还可以,但是过大的值将会让程序进入大量计算,很大可能会导致栈溢出。
而我们用迭代的方式,就没有那么大的计算量。
#include <stdio.h>
int main()
{
int a = 1;
int b = 1;
int c = 1;
int num = 0;
scanf("%d", &num);
if (num <= 2)
{
printf("%d", c);
}
else
{
int i = 0;
for (i = 2;i < num;i++)
{
c = a + b;
a = b;
b = c;
}
printf("%d", c);
}
return 0;
}