从0开始学c语言-14-关于(3)函数递归、递归与迭代、栈溢出、练习求第n个斐波那契数、用递归思想求字符串的长度

本人0基础开始学编程,我能学会的,你也一定可以,学会多少写多少。

下载安装请从官网入手,社区版本即可,这里主要使用的软件是VS2019,图标如下。

 上一篇:从0开始学c语言-14-关于(2)函数的嵌套调用和链式访问、函数的声明和定义、静态库_阿秋的阿秋不是阿秋的博客-CSDN博客

目录

七、函数递归

1·递归是什么

2·递归的两个必要条件

3·代码演示

Stack overflow 栈溢出

练习

***(新知识)***这里我先介绍一下应该知道的知识点。

秉持大事化小的思维

4·递归和迭代

练习1

练习2

解决栈溢出的问题

总结:

验证一下你学会了多少


七、函数递归

1·递归是什么

程序调用自身的编程技巧称为递归( recursion)。
        递归做为一种算法在程序设计语言中广泛应用。 一个过程或函数在其定义或说明中有直接或间接 调用自身的 一种方法,它通常把 个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题 来求解, 递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。
递归的主要思考方式在于:把大事化小。

2·递归的两个必要条件

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

3·代码演示

接受一个整型值(无符号),按照顺序打印它的每一位。

例如:输入1234,打印1 2 3 4

先写出主函数,

int main()
{
	unsigned int a = 0; //无符号整形变量a
	scanf("%u", &a);  //%u打印无符号整型,别忘记取地址
	aqiu(a);  //我们定义这个函数打印输入值的每一位
	return 0;
}

那么关键是如何设计这个函数,我们采用递归的方式来思考,

1·大事化小

2·有限制条件

3·每次调用接近限制条件

首先考虑,1234怎么运算就可以输出4了呢?我们可以让1234除上10,那么余数就会是4。

那么该怎么得到3呢?让123除10,余数是3。

以此类推就像是这样

 1·大事化小

2·限制条件

也许你想不到限制条件,那是因为你不知道限制条件该用在哪里,起到什么作用。

这里提醒一下,每次我们调用输入值的时候都是通过输入值除10来获得下一个输入值,那么我们什么时候就不需要在除10来获得下一个输入值了呢?这里放一个图。

当输入值为9的时候,我们所获得的下一个输入值为0,这显然不是我们想要的下一个输入值,所以我们这里设定限制条件:输入值>9的时候需要/10

3·每次调用接近限制条件

可以看到,每次/10来获得的下一个输入值在不断减小,我们已经达到每次调用接近限制条件。

那么现在可以写出这个函数了。

void aqiu(unsigned int b)
{
	if (b > 9) //限制条件
	{
		aqiu(b / 10);  //调用接近限制条件
	}
	printf("%u ", b%10);
}


int main()
{
	unsigned int a = 0;
	scanf("%u", &a);  //%u打印无符号整型,别忘记取地址
	aqiu(a);  //这个函数打印输入值的每一位
	return 0;
}

 可能你还不是很理解,我们画图示意一下。

Stack overflow 栈溢出

我们用这段代码看看什么叫栈溢出。

void qiu(int b)
{
	if (b <900000)
	{
		qiu(b + 1);  //开始调用,一下子走完这里会出警告
	}
}
int main()
{
	qiu(1);
	return 0;
}

每一次调用函数都要在栈区分配栈帧空间,递归太多次就不够用了~

所以我们可以总结一下递归:

秉持大事化小的思维

1、设置跳出条件

2、调用会逼近跳出条件

3、递归层次不能太深

练习

题目:编写函数不允许创建临时变量,求字符串的长度。

请使用递归思想。

***(新知识)***这里我先介绍一下应该知道的知识点。

代码是这样的,不需要运行,调试看数组和函数参数关系就好。

int bbc(char* s) 
{
	printf("%p\n", s);
	*s++;
	s++;
	//printf("%p\n", *s);
	return 1;
}
int main()
{
	char qiu[] = "sadday";
	printf("%p\n", qiu);  //数组本身就是地址,不需要&
	bbc(qiu);
	return 0;
}

 从上图我们可以看到qiu数组中有6个元素,qiu[0]代表数组的首元素。

 可以看到,上图中的s是传过去的qiu数组的首元素地址。

 s++可以得到下一个元素的地址并且首元素变为s++所对应的那个元素。

*s代表的是元素,不包含地址

*(s+1)可以得到下一个元素,但是*s+1则代表了元素所对应的ASCII码表所对应的十进制数字。

s和s+1是char*类型,并且包含所对应的char类型元素。

*s和*(s+1)是char类型。

有了这些知识就可以写出我们需要的代码了。

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

int len(char* s)
{
	int count = 0;
	while (*s != '\0')
	{
		count++;
		s++;   //写成*s++也能运行会警告,但是我没懂具体原因是什么
	}
	return count;
}

int main()
{
	char a[] = "sadasd";
	printf("%d\n", len(a));  //打印数组a的字符串长度
	return 0;
}

但是这不是递归思想。

拿出来我们之前总结的东西来思考

秉持大事化小的思维

我们把sad字符拆成求sa和d的长度,再把sa拆成求s和a的长度。

1、设置跳出条件

条件挺明显的把?这里再提示一下:我们传过去的是首元素的地址哦!

2、调用会逼近跳出条件

试着每次调用把参数发生变化一下,既然是字符串的话,你能改变什么呢?

3、递归层次不能太深

这个应该不会涉及到。

然后就敲出来了。

int len(char* s)
{
	if (*s != '\0')
	{
		return 1 + len(s + 1); //和上面的递归思维一样,大事化小
	}
	else
		return 0;
}

int main()
{
	char aqiu[] = "asdadd";
	printf("%d\n", len(aqiu));  
}

这里不再画图示意,大家要学着自己画图梳理思路。

4·递归和迭代

练习1

我们写一个数的阶乘代码,会是这样。这叫迭代。

int main()
{
	int a = 0;
	scanf("%d", &a); //输入想阶乘的数,别随便加空格和\n
	int b = 0;  //设定需要的数
	int jiec = 1; //需要输出的阶乘结果
	for (b = 1; b <= a; b++)
	{
		jiec = jiec * b;
	}
	printf("%d\n", jiec);
	return 0;
}

现在采用递归的思想。

 那么代码就会是这样

int jiec(int b)
{
	if (b <= 1)
		return 1;
	else
		return b * jiec(b - 1); //不懂就画图理解
}
int main()
{
	int a = 0;
	scanf("%d", &a);
	printf("%d\n", jiec(a));
	return 0;
}

练习2

求第n个斐波那契数。

 这就是这个数的内部逻辑。

那么梳理一下它的思路就会是这样。

 用递归的思想就会写成这样

int fib(int n) 
{
     if (n <= 2)         
     return 1;
     else
     return fib(n - 1) + fib(n - 2);
}
但是在运行的时候效率很低,因为栈溢出了。系统分配给程序的栈空间是有限的,但是如果出现了死循环,或者(死递归),这样有可能导致一直开辟栈空间,最终产生栈空间耗尽的情况。

解决栈溢出的问题

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

while(n>2)
{
    c=a+b;
    a=b;
    b=c;
    n--;
}
这里只写出核心迭代部分的代码。

总结:

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

验证一下你学会了多少

把函数处理结果的两个数据返回给主函数,在下面的方法中不正确的是

A.return这两个数
B.形参用数组
C.形参用两个指针
D.用两个全局变量

答案:a

下一篇:从0开始学c语言-15-一维数组与二维数组的创建、初始化以及在内存中的储存、数组越界和数组作为函数参数_阿秋的阿秋不是阿秋的博客-CSDN博客

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值