[C语言学习]----函数递归(超详细!!!)

本篇介绍的是C语言函数递归的详细知识

程序的艺术来源于生活

目录

7. 函数递归

7.1递归是什么

7.2 递归的两个必要条件

7.2.1练习1(详细讲解)

7.2.2练习2(详细讲解)

7.3 递归与迭代

 7.3.1练习3(详细讲解)

7.3.2 练习4(详细讲解)

7.4练习3,4中出现的问题


7. 函数递归

7.1递归是什么

程序调用自身的编程技巧称为递归

函数自己调用自己


递归做为一种算法在程序设计语言中广泛应用。 一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解

递归策略

只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。


递归的主要思想在于:把大事化小

例(史上最简单的递归):

#include<stdio.h>
int main()
{
	printf("Hello World\n");
	main();
}

先一直打印 Hello World,最终程序挂掉

7.2 递归的两个必要条件

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

7.2.1练习1(详细讲解)


接受一个整型值(无符号),按照顺序打印它的每一位。
例如:
输入:1234,输出 1 2 3 4

 

我们的第一想法是先让

	1234%10 = 4
	1234/10=123
	123%10=3
	123/10=12
	12%10=2
	12/10=1
	1%10=1
	1/10=0

这样能够得到数字的每一位,然后我们再打印出来

#include<stdio.h>
int main()
{
	unsigned int num = 0;
	scanf("%u", &num);
	while (num)
	{
		printf("%d ", num % 10);
		num = num / 10;
	}
	return 0;
}

程序一进行我们发现输出的结果与题目中的不同,需要我们用递归来进行

递归的思想是将大事化小

我们定义一个print()函数,它的功能是按照顺序打印num的每一位

打印1234的每一位:print(1234)

打印1234的每一位,我们可以先打印123的每一位再打印4

打印123的每一位:我们可以先打印12的每一位print(12)再打印3 

打印12的每一位:我们可以先打印1的每一位print(1)再打印2 

最后输出打印1 2 3 4

程序:

print(1234)
print(123) 4
print(12) 3 4
print(1) 2 3 4
最终输出:1 2 3 4

num<10的时候我们只需要直接打印就行

num>=10.我们就需要分着打印 

代码实现:

#include<stdio.h>
void print(unsigned int n)
{
	if (n < 10)//限制条件
		printf("%d ", n);
	else
	{
		print(n / 10);//每次递归调用之后越来越接近这个限制条件
		printf("%d ", n%10);
	}
}
int main()
{
	unsigned int num = 0;
	scanf("%u", &num);
	print(num);
	return 0;
}


当然也有更简单不冗余的写法:

#include<stdio.h>
void print(unsigned int n)
{
	if (n > 9)限制条件
	{
		print(n / 10);每次递归调用之后越来越接近这个限制条件
	}
	printf("%d ", n % 10);
}
int main()
{
	unsigned int num = 0;
	scanf("%u", &num);
	print(num);
	return 0;
}

7.2.2练习2(详细讲解)

编写函数(不允许创建临时变量),求字符串的长度

铺垫(编写函数(允许创建临时变量),求字符串的长度):

我们有一个专门求字符串长度的库函数:strlen

 首先我们先创建一个字符数组

char arr[] = "abc";

再用strlen

#include<stdio.h>
#include<string.h>
int main()
{
	char arr[] = "abc";
	int len = strlen(arr);//传递的首元素地址
	printf("%d", len);
	return 0;
}

这题是让我们创建一个函数my_strlen,模仿写一个strlen函数

非递归方式:

#include<stdio.h>
int my_strlen(char* str)
{
	int count = 0;
	while (*str != '\0')
	{
		count++;
		str++;
	}
	return count;
}
int main()
{
	char arr[] = "abc";
	int len = my_strlen(arr);
	printf("%d", len);
	return 0;
}

 编写函数(不允许创建临时变量),求字符串的长度(递归方式):

如果第一个字符不是'\0',说明字符串里面至少包含一个字符

所以当第一个元素不是'\0'的话

求my_strlen("abc")就可以看成求1+my_strlen("bc")的长度

求1+my_strlen("bc")的长度就可以看成 求1+1+my_strlen("c")的长度

求1+1+my_strlen("c")的长度就可以看成 求1+1+1+my_strlen(" ")的长度

最后算出来等于3

 代码实现:

#include<stdio.h>
int my_strlen(char* str)
{
	if (*str != '\0')
		return 1 + my_strlen(str + 1);//传递下一个字符的地址
	else
		return 0;
}
int main()
{
	char arr[] = "abc";
	int len = my_strlen(arr);
	printf("%d", len);
	return 0;
}

7.3 递归与迭代

 7.3.1练习3(详细讲解)

求n的阶乘。(不考虑溢出

递归:

#include<stdio.h>
int Fac(int n)
{
	if (n <= 1)
		return 1;
	else
		return n * Fac(n - 1);
}
int main()
{
	int n = 0;
	scanf("%d",&n);
	printf("%d",Fac(n));
	return 0;
}

但当我们输入大的数字时,递归会算不出结果

所以这道题最好用非递归的方式(迭代)计算:
(循环是一种迭代)

#include<stdio.h>
int Fac1(int n)
{
	int i = 0;
	int ret = 1;
	for (i = 1; i <= n; i++)
	{
		ret = ret*i;
	}
	return ret;
}
int main()
{
	int n = 0;
	scanf("%d",&n);
	printf("%d",Fac1(n));
	return 0;
}

7.3.2 练习4(详细讲解)

求第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",Fib(n));
	return 0;
}

7.4练习3,4中出现的问题

但是我们发现这样会有问题:
 

  • 在使用 Fib 这个函数的时候如果我们要计算第50个斐波那契数字的时候特别耗费时间。
  • 使用Fac函数求10000的阶乘(不考虑结果的正确性),程序会崩溃。

原因是因为

Fib 函数在调用的过程中很多计算其实在一直重复
那我们如何改进呢?

  • 在调试 Fac函数的时候,如果你的参数比较大,那就会报错: stack overflow(栈溢出)这样的信息。
  • 系统分配给程序的栈空间是有限的,但是如果出现了死循环,或者(死递归),这样有可能导致一直开辟栈空间,最终产生栈空间耗尽的情况,这样的现象我们称为栈溢出

如何解决上述问题

1. 将递归改写成非递归。
2. 使用static对象替代 nonstatic 局部对象。在递归函数设计中,可以使用 static 对象替代
nonstatic 局部对象(即栈对象),这不仅可以减少每次递归调用和返回时产生和释放 nonstatic 对象的开销,而且 static 对象还可以保存递归调用的中间状态,并且可为各个调用层所访问

非递归方式解决斐波那契数列

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

提示:


1. 许多问题是以递归的形式进行解释的,这只是因为它比非递归的形式更为清晰。
2. 但是这些问题的迭代实现往往比递归实现效率更高,虽然代码的可读性稍微差些。
3. 当一个问题相当复杂,难以用迭代实现时,此时递归实现的简洁性便可以补偿它所带来的运行时开销

 

结语:本篇是函数递归的知识,如果本篇对你有用,请大家点赞关注!!!

现在关注是新粉,以后就是老粉了

下期预告:数组

感谢你的观看,我们下期再见

  • 45
    点赞
  • 205
    收藏
    觉得还不错? 一键收藏
  • 11
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值