【C语音】——函数递归

一、 什么是递归

1.1 递归是什么

  
  学习C语言函数,递归是永远绕不过去的一个话题,那么。什么是递归呢?
  递归其实是一种解决问题的方法,在C语言中,递归就是自己调用自己
  
  下面列举一个最简单的C语言递归代码:

#include<stdio.h>

int main()
{
	printf("hello world\t");
	main();
	return 0;
}

  上述代码即是main函数不断调用main函数,即不断自己不断调用自己。但上面的代码只是为了演示递归的基本形式,不是为了解决问题。因为代码不断自己调用自己,一直无法停止,所以该代码最终会陷入死循环,导致栈溢出1(Stack overflow)
  
  注:Stack overflow同时也是一个程序员技术问答社区网站

1.2 递归的思想

  把一个复杂的问题转换为由多个小问题的组成,同时,分割的无数个小问题又与原问题类似,如此来进行求解;当小问题不能再被拆分,问题解决,递归结束。递归的思想就是大事化小,小事化了
  
  递归中的递就是递推的意思,归就是回归的意思。

  • 递:程序不断深入地调用自身,通常传入更小或更简化的参数,直到达到“终止条件”。
  • 归:触发“终止条件”后,程序从最深层的递归函数开始逐层返回,汇聚每一层的结果。

1.3 递归的必要条件

  
  相信大家看了前面的例子,都会意识到:递归不能让他自己乱跑,必须有一个限制条件,不然程序容易陷入死循环。
  其实,限制条件正是递归的其中一个必要条件。
  
递归的两个必要条件:

  • 递归存在限制条件,当满足这个限制条件时,递归终止。
  • 每一次递归之后,需越来越接近这个限制条件。

二、 递归举例

2.1、例一: 计算 N 阶乘

  计算 n n n 的阶乘,用以前学过的知识想必大家都会
  
参考代码:

#include<stdio.h>

int main()
{
	int n = 0;
	scanf("%d", &n);
	int sum = 1;
	int i = 1;
	for (i = 1; i <= n; i++)
	{
		sum *= i;
	}
	printf("%d\n", sum);
	return 0;
}

  那么运用递归的方法又该怎么写呢?
  
  我们都知道,阶乘的公式: n ! = n ∗ ( n − 1 ) n! = n*(n-1) n!=n(n1),同理,即可退出 ( n − 1 ) ! = ( n − 1 ) ∗ ( n − 2 ) (n-1)! = (n-1)*(n-2) (n1)!=(n1)(n2)
  那么,递归的思路就有了,当 n = = 0 n==0 n==0 时,结果为 1 ,而当 n > 1 n>1 n>1 时,都可以通过公式进行递推。
  
图示:

在这里插入图片描述

  
参考代码:

#include<stdio.h>
//递归函数
int Fact(int n)
{
	if (n == 0)
		return 1;
	else
		return n * Fact(n - 1);
}

int main()
{
	int n = 0;
	scanf("%d", &n);
	int sum = Fact(n);
	printf("%d\n", sum);
	return 0;
}
  •  递归的条件: n > 0 n > 0 n>0
  •  限制条件: n = 0 n = 0 n=0
  •  每递推一次, n n n减一,不断逼近限制条件


画图推演:

在这里插入图片描述

  

2.2、 例二:逆序打印一个整数的每一位

  
  用以前的知识,我们可以这样写:

#include<stdio.h>

int main()
{
	int n = 0;
	scanf("%d", &n);
	while (n)
	{
		printf("%d ", n % 10);
		n /= 10;
	}
	return 0;
}

  其实,运用以前的知识,不难发现里面也隐藏着递归的思想:当该数为个位数时,打印他自身;当不是个位数,打印他最后一位数,并让他的位数不断减小就行了。
  
参考代码:

#include<stdio.h>

void Print(int n)
{
	if (n < 10)
		printf("%d ", n);
	else
	{
		printf("%d ", n % 10);
		return Print(n / 10);
	}
}

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

  当然,Print函数还可以如下简化:

void Print(int n)
{
	printf("%d ", n % 10);
	if(n >= 10)
	{
		return Print(n / 10);
	}
}

  
图片演示:

这里是引用

  

三、 递归与迭代

3.1、 递归的缺点

  
  递归是一种解决问题的好方法,但我们也需要了解递归的缺点,防止递归被误用。
  
  像前面的求 n n n 的阶乘:在C语言中,函数的每一次调用,都用为函数申请一块空间,该空间称为函数栈帧
  而函数递归,在递推阶段时,函数每一次调用自身都用像内存申请空间,如果函数不返回,这些空间会被一直占用,直到回归阶段,函数才开始返回,才开始由深到浅逐层释放栈帧空间。
  当递归的层次太深,就会浪费太多栈帧空间,也有可能引起栈溢出问题(stack overflow)。
  
  事实上,递归解决问题的效率并不高。

3.2、 用递归解斐波那契数列

  下面我们用一个例子可以更加形象理解这一点:
  
求第 n 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);
	printf("%d\n", Fib(n));
	return 0;
}

  但是,执行代码我们发现:当输入一个较大的数时,计算结果要很长时间才能求出来,这表明计算过程太过冗长,效率太低

  为什么会这么慢呢?我们来统计一下计算 n = = 40 n == 40 n==40 时, F i b ( 3 ) Fib(3) Fib3计算了多少次

#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);
	printf("结果 = %d\n", Fib(n));
	printf("计算了%d次\n", count);
	return 0;
}

  
运行结果:
在这里插入图片描述  如图,仅仅是 n = = 3 n == 3 n==3就计算了这么多次,那还有 4 , 5 , 6 ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ 4, 5, 6······ 4,5,6⋅⋅⋅⋅⋅⋅可想而知计算过程是有多么冗余了。
  
  为什么会这样呢?
图解:

在这里插入图片描述

注:虽然这代码进行了许多重复计算,但并没有栈溢出,因为他的层次并不是特别深,当 n = = 50 n == 50 n==50 时,层次最多50层。

3.3、 什么是迭代

  
迭代的定义

  迭代是重复反馈过程的活动,其目的通常时为了逼近所需目的或结果。每一次对过程的重复称为一次迭代,而每一次迭代得到的结果会作为下一次迭代的初始值。
  
  可以说迭代就是一种不断用变量的旧值递推出新值得过程,一般会用一个计数器来判断是否结束迭代。
  
  在现阶段的学习中,我们可以简单地认为迭代通常就是循环

3.4、 用迭代解斐波那契数列

  
解题思路:
  我们都知道,斐波那契数列前两个数是 1 1 1 ,后面的数是前两个数的相加,那么只要我们从前往后,不断更新前两个数的值,从小到大计算就行了。
  
参考代码:

#include<stdio.h>

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

  用迭代的方法去实现这个代码,效率就要高很多了。
  

3.5、 迭代与递归总结

  可能我们看到的许多问题都是用递归的思想进行解释的,是因为递归思想理解起来更容易、更清晰。但事实上,递归解决问题的效率并不高。
  
  有时候,写一个代码,虽然递归难以想到,但是使用递归写出的代码会非常简单,往往一个代码使用递归可能就是几行代码,而写成非递归(迭代)的方式,就得十几行甚至几十行代码。因此递归实现的这种简洁性,就弥补了效率低下的缺点。
  但如果递归的不恰当书写,会导致一些无法接受的后果,我们应该放弃使用递归,使用迭代来解决问题。
  
  后期学习数据结构的时候,经常会使用递归
  在面试(笔试)的时候,你使用递归很快的解决了代码问题,但遇到“变态”的面试官,想考察你的代码能力,就会让你改成非递归(迭代)~

  
  

  好啦,本期关于函数递归就介绍到这里啦,希望本期博客能对你有所帮助,同时,如果有错误的地方请多多指正,让我们在C语言的学习路上一起进步!


  1. 栈溢出:每一次函数的调用都会向内存(栈区)申请空间来保存函数调用期间各种变量的值,称为:函数栈帧,而内存是有限的,超出内存的存储空间则为栈溢出↩︎

  • 32
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值