函数递归
1 什么是递归![](https://i-blog.csdnimg.cn/blog_migrate/d68860aa1ac091a215a169778dfe76af.jpeg)
递归是在C语言中绕不开的一个话题,那究竟什么是递归呢?
递归,就是函数在运行的过程中调用自己的就叫递归。
最简单的C语言递归代码:
#include<stdio.h>
int main()
{
printf("xixxixi\n");
main();//main函数中又调用了main函数
return 0;
}
上述就是一个简单的递归函数,上面的代码是为了展示递归的基本形式,不是为了解决问题,最终代码会陷入死循环里,导致栈溢出来。
就会这样子。
递归的思想
把一个大型复杂问题层层转化为一个与原问题相识,但规模较小的子问题来求解,知道子问题不允许再拆分,递归就结束。即递归的问题就是把大事化小的一个过程。
2 递归中的限制条件
在书写递归的时候,有2个必要条件:
递归存在限制条件,当满足这个限制条件的时候,递归便不再继续。(终止条件)
每次递归调用之后越来越接近这个限制条件。(调用自己)
3 递归的举例
举例1:求n的阶乘
一个正整数的阶乘是所有小于及等于该数的正整数的积,并且0!=1。
自然数n的阶乘写成n!
分析和代码实现
首先,我们要知道n的阶乘计算公式:n!=n*(n-1)!
例如:5!=5*4*3*2*1
4!=4*3*2*1
所 以5!=5*4!
这样子我们可以明显的知道这思路就是把一个比较大的问题转化为一个与原问题相似,但规模较小的问题来解决。
当n==0的时候,0!=1,其余的阶乘都可以通过阶乘计算公式来计算 n!=n*(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;
printf("请输入一个数:");
scanf("%d", &n);
int sum = Fact(n);
printf("%d\n", sum);
return 0;
}
运行结果入下:
从该代码中,我们知道递归的条件是:n>0
递归停下来的条件是n==0
不断逼近跳出条件
图画推演
大家也可以通过这张图画推演
拿Fact(5)来推演
举例2:顺序打印一个整数的每一位
输入 一个数m,打印这个按照顺序打印整数的每一位
如:输入m为1234 输出为1 2 3 4
分析和代码的实现
首先,我们会想到怎么可以得到每一位数字呢?
如果n是一位数的话,输出的就是n
n超过一位数的话,就要拆分每一位,那具体要怎样才能实现呢
拿n=1234来为例
1234%10就能得到4,然后1234/10=123,这就相当于去掉了4
然后对123%10就能得到3,再除10去掉3,我们可以以此类推
不断的%10和/10的操作,知道1234的每一位都得到
但是大家发现了没,如果按照这样子打印的话,我们得到的是 4 3 2 1但是我们要的是1 2 3 4,只有该怎么办呢?
我们发现其实一个数字的最低位是最容易得到的,通过%10就能得到了(1234%10=4)
那我们可以假设想写一个函数Print来打印n的每一位,如:
Print(n)
/如果n是1234,那表示为
Print(1234)//打印1234的每一位
/其中1234中的4可以通过1234%10得到,那么
Print(1234)可以拆分为两步:
Print(1234/10)//打印123的每一位
Print(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)
直接被打印的数字就变成一位位小数,就不需要我们再拆分了,递归就结束了。
那么完整的代码如下:
#include<stdio.h>
int Print(int n)
{
if (n > 9)
{
Print(n / 10);
}
printf("%d ", n % 10);
}
int main()
{
int m;
printf("请输入一个整数:");
scanf("%d", &m);
int c = Print(m);
return 0;
}
从代码中:
递归停下来的条件是n<=0
n/10 不断逼近跳出条件
运行结果:
这个过程就是:使用了大事化小的思路
把Print(1234)打印的1234每一位,拆解为首先Print(123)打印的每一位,再打印得到4
把Print(123)打印的123每一位,拆解为首先Print(12)打印的每一位,再打印得到3
直到Print打印的是一位数,直接打印
图画推演
4 递归与迭代
递归是一种很好的编程技巧,但是很多技巧都一样,也是可能被误用的,就像举例1:求n的阶乘
事实上,我们看到的许多问题是以递归的形式进行解释的,这只是因为它比非递归的形式更加清晰, 但是这些问题的迭代实现往往比递归的实现效率高。
当一个问题非常复杂,难以用迭代的方式实现时,此时递归实现的简洁便可以补偿它所带来的运行开销。
举例3:求n个斐波那契数
如,计算第n个斐波契数,是不适合用递归求解的,但是可以使用递归的形式来描述的,如下:
看到这个,大家很容易这么写代码的
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;
printf("请输入一个数字:");
scanf("%d", &n);
int ret = Fib(n);
printf("第%d个斐波那契数是%d", n, ret);
return 0;
}
当n=50时,需要很长时间来计算
其实递归程序会不断地展开,在展开过程中,我们很容易发现,在递归的过程中会有重复计算,而且层次越来越深。
我们可以测试当n=40契数等于多少
我们可以定义一个变量count来计算当n=50时,一共计算了几次
代码如下:
#include<stdio.h>
int count = 0;
int Fib(int n)
{
if (n == 3)
count++;
if (n<=2)
return 1;
else
return Fib(n - 1) +Fib (n - 2);
}
int main()
{
int n = 0;
printf("请输入一个数字:");
scanf("%d", &n);
int ret = Fib(n);
printf("第%d个斐波那契数是%d\n", n, ret);
printf("第三个斐波契数一共打印%d次\n",count);
return 0;
}
运行结果:
在这里我们可以看见,在计算第40个斐波契数的时候,使用递归的方法,第三个斐波契数就被计算了39088169次。所以斐波契数的计算,使用递归是非常不明智的,我们可以用迭代来解决。
如下:
#include<stdio.h>
int Fib(int n)
{
int a = 1;
int b = 1;
int c = 1;
while (n >= 3)
{
c = a + b;
a = b;
b = c;
n--;
}
return c;
}
int main()
{
int n = 0;
printf("请输入一个数字:");
scanf("%d", &n);
int sum = Fib(n);
printf("第%d个斐波那契数是%d", n, sum);
return 0;
}
代码运行如下:
当n=5000时,我们可以看到代码很快就有结果了,但结果是错的,因为int存不了那么大的数。
小伙伴们,这章就学完了。