学习C语言(八)——函数递归

        今天我们来更深层次的学习函数——函数递归。

目录

1.递归是什么?

2.递归的限制条件

3.递归举例

3.1 举例1: 求n的阶乘

3.1.1 分析

3.1.2 画图推演

3.1.3 代码实现

3.2 举例2:顺序打印一个整除的每一位

3.1.1 代码分析

3.1.2 图画推演

 3.1.3 代码实现

 4.递归与迭代

4.1 递归和迭代的对比

4.2 举例3:斐波那契数


1.递归是什么?

        递归是C语言中绕不开的话题。那么首先,什么是递归?

        递归就是在函数中调用自己。

        递归的思想:将一个大型复杂的问题层层转化,转变为一个小步骤通过多次重复,直到最后子问题不能够再被拆分,递归就结束了,达到解决复杂问题的目的。所以递归的思考方式就是把大事化小的过程。

        递归中的递就是递推,归就是回归

2.递归的限制条件

        当然递归也不是能够一直进行的,每次在递推中调用函数时,就会在栈区产生一个新的局部变量,这个新的局部变量就会占用空间,最后栈溢出,导致代码运行错误。

        递归有两个必要条件:

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

3.递归举例

3.1 举例1: 求n的阶乘

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

3.1.1 分析

        首先需要知道n的阶乘就是,从1乘到n,阶乘公式就是 n!= n * (n-1)。

举例:
        5!= 5 * 4 * 3 * 2 * 1
        4!= 4 * 3 * 2 * 1
        3!= 3 * 2 * 1
        2!= 2 * 1
        1!= 1

    所以:2!= 2 * 1!
         3!= 3 * 2!
         4!= 4 * 3!
         5!= 5 * 4!
    
         n!= n * ( n - 1 )!
         ( n - 1 )! = ( n - 1 ) * ( n - 2 )!

        通过上面的这样的转化,就可以把一个复杂的问题,转化为一个可以通过一个重复但规模小的问题求解,知道n为1或0时,便不在拆解。

        再分析一些,就可以得到n阶乘的递推公式。当 n<=1 时,n的阶乘是 Fact(1)=1;当 n>1  时,Fact(n)=n*Face(n-1)

3.1.2 画图推演

3.1.3 代码实现

        定义一个 Fib 函数,通过函数递归来实现求n的阶乘 

3.2 举例2:顺序打印一个整除的每一位

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

3.1.1 代码分析

        如果n是一位数,n的每一位就是n自己。当n超过一位数时,就需要拆分每一位。      

        比如:1234%10就能得到4,然后1234/10得到了123,相当于去掉了4. 然后123%10就得到了3,123/10就去掉了3,以此类推。通过不断的%10和/10,就能够得到1234的每一位。

        但是这样先得到的就是个位数了,要让它们按照顺序一个个打印出来,可以将打印放到后面,

3.1.2 图画推演

 3.1.3 代码实现

 4.递归与迭代

4.1 递归和迭代的对比

        递归是一种很好的编程技巧,但是也很容易在一些能用递归的情况下被误用。

        例如在举例1中,Fact函数时可以产生正确的结果,但是在递归函数调用的过程中涉及一些运行时的开销。在C语言中每一次函数调用,都需要为本次函数调用在栈区申请一块内存空间来保存函数调用期间的各种局部变量的值。这块内存空间被称为运行时堆栈,或者函数堆栈。

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

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

        如果不想使用函数递归的话,通常就是使用迭代的方式(通常就是循环的方式)。

        比如还是举例1中,计算n的阶乘。使用循环的方式也可以得到n的阶乘,而且不会占用过多的空间,而且效率比递归的方式更好。

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

4.2 举例3:斐波那契数

        计算第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",ret);
	return 0;
}

        当输入n为50时,需要很长时机你才能算出结果,这个计算所花费的时间,是难以接受的。

        递归程序会不断展开,在展开的过程中,很容易就能发现递归过程中会有大量的重复计算,而且递归层次越深,冗余计算就会越多。示例如下:

         通过图片我们使用递归的方式计算第40个斐波那契数时,第3个斐波那契数就被重复计算了非常大的次数,这些都是冗余。

        我们知道斐波那契数的前两个数都是1,然后前两个数相加就是第三个数。那么使用迭代的方式既可以轻松计算。

 

        使用迭代的方式就能够很快算出来,比递归效率高出很多。

        所以递归虽好,但也会有一些问题。 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值