『C语言初阶』第三章-函数(3)

1.什么是递归

        递归其实就是一种解决问题的方法,在C语言中,递归就是函数自己调用自己。

#include<stdio.h>

int main()
{
    printf("hehe\n");
    main();  // main函数中调用main函数
    return 0;
}

        上述就是一个简单的递归程序,不过这个代码就只是简单的反复递推,没有回归 。所以代码最终就会陷入死递归,导致栈溢出(Stack overflow)

        注:stackoverflow.con 也是一个国外程序员技术答疑网站。

        递归的思想:

        把一个大型复杂问题层层转化为一种与原问题相似,但规模较小的子问题来求解;直到子问题不能再被拆分,递归就结束了。所以递归的思考方式就是将大事化小的过程。

        递归中的递就是递推的意思,归就是回归的意思。

        递归其实就是函数依次进栈,之后依次出栈的过程,不过,出栈时是 先进后出的原则

2.递归的限制条件

        递归在书写的时候,有2个必要条件:

  •  递归存在限制条件,当满足这个限制条件的时候,递归便不再继续。
  • 每次递归调用之后越来越接近这个限制条件。

        同样的递归也有两个特性

  • 存在一个合理的且一定会达到的回归条件
  • 每次的递归一定存在一个递归公式。

        当我们找到了这两个这两个条件之后,我们就可以利用这两个条件来构建一个递归

3.递归举例

    3.1  举例1 :求n的阶乘

        计算n的阶乘(不用考虑数据溢出),n的阶乘就是1~n的数字累积相乘。

        通过分析我们知道了 n的阶乘公式为: n! = n*(n-1)!

        同时,当n为1的时候停止。

        所以我们列出n的阶乘的递归公式如下:

        之后,我们就可以通过公式转换为代码

#include<stdio.h>

int fact(int n)
{
    if(n<=1)    return 1;
    else return n*fact(n-1);
}


int main()
{
    int n;
    scanf("%d",&n);
    int ret=fact(n);
    printf("%d\n",ret);
    return 0;
}

        注:这里不考虑n太大的问题,一般阶乘函数返回值均为double类型。

 3.2 顺序打印一个整数的每一位

        输⼊⼀个整数m,打印这个按照顺序打印整数的每⼀位。

         比如: 输⼊:1234 输出:1 2 3 4

                      输⼊:520 输出:5 2 0

        分析:

                函数递归实质上就是进栈,出栈,先进先出,所以我们让输入的m值每次除以10,直到递推到m<10的时候开始回归,回归的同时输出每一个m的个位 就完成了

        lu

        落实到代码上就是

#include <stdio.h>
void print(int n)
{
    if(n<10) printf("%d",n);
    else 
    {
        print(n/10);
        printf("%d ",n%10);    
    }    
}


int main()
{
    int m;
    scanf("%d",&m);
    print(m);
    return 0;
}

4.递归与迭代

        递归是一种很好的编程技巧,但是和很多技巧一样,也是可能会被误用,就像举例1一样,看到推导公式,就很容易写成递归的形式

        但需要注意的是递归函数调用的过程中涉及一些运行时的开销。

        在C语言中每一次函数调用,都需要为本次函数调用在栈区申请一块内存空间来保存函数调用期间的各种局部变量的值,这块空间被称为运行时堆栈,或者函数栈帧。

        函数不返回,函数对应的栈帧空间就一直占用,所以如果函数调用中存在递归调用的话,每一次递归函数调用都会开辟属于自己的栈帧空间,直到函数递归不再继续,开始回归,才逐层释放栈帧空间。

        所以如果采用函数递归的方式完成代码,递归层次太深,就会浪费太多的栈帧空间,也可能引起栈溢出(stack overflow)的问题。

        所以如果不想使用递归就得想其它的办法,通常就是迭代的方法(通常就是循环)

        比如:计算n的阶乘,也是可以产生1~n的数字累计乘在一起的。

int Fact(int n)
{
 int i = 0;
 int ret = 1;
 for(i=1; i<=n; i++)
 {
 ret *= i;
 }
 return ret;
}

        上述代码是能够完成任务的,并且效率是比递归更好的。

        事实上,我们看到的许多问题是以递归的形式进行解释的,这只是因为它比非递归的形式更加清晰,但是这个问题的迭代实现往往比递归实现效率更高。

        当一个问题非常复杂,难以使用迭代的方式实现时,此时递归实现的简洁性便可以补偿它所带来的运行时的开销。

        举例3:求第n个斐波那契数

        我们也能举出更加极端的例⼦,就像计算第n个斐波那契数,是不适合使⽤递归求解的,但是斐波那契 数的问题通过是使⽤递归的形式描述的,如下:

                  

        转换为代码就是

#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;
 scanf("%d",&n);
 int ret = Fib(n);
 printf("%d\n", ret);
 return 0;
}

        当我们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;
 scanf("%d",&n);
 int ret = Fib(n);
 printf("%d\n", ret);
 printf("\ncount = %d\n", count);
 return 0;
}

        输出结果: 

        这⾥我们看到了,在计算第40个斐波那契数的时候,使⽤递归⽅式,第3个斐波那契数就被重复计算了 39088169次,这些计算是⾮常冗余的。所以斐波那契数的计算,使⽤递归是⾮常不明智的,我们就得 想迭代的⽅式解决。

        所以这里我们采用一种迭代的方式解决。

        我们直到斐波那契数的前2个数都1,然后前2个数相加就是第3个数,那么我们从前往后,从小到大计算就行了。

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;
}

        而这个迭代的方法,效率就会高很多了。

        有时候,递归思路简单,但是实现时也会遇到一些问题,所以我们一定不要迷恋递归,适可而止就好。

  • 19
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值