6.函数的递归

目录

1.什么是递归?

2.递归的必要条件

3.具体示例讲解

示例1:创建my_strlen函数,模拟实现strlen功能。

示例2:求斐波那契数

示例3:求水仙花数

4.递归与迭代

递归与迭代的区别

用迭代的方式求斐波那契数

5.递归练习题


1.什么是递归?

程序调用自身的编程技巧称为递归( recursion)。

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

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

递归的主要思考方式在于:把大事化小

2.递归的必要条件

1、递归必须满足限制条件,当满足这个条件时,递归停止。

2、每一次递归后都必须接近限制条件。

递归的使用必须同时满足以上的两个条件,当其中之一不满足时,函数将陷入死递归。

3.具体示例讲解

示例1:创建my_strlen函数,模拟实现strlen功能。

迭代方法

核心思想

  • strlen是用来求字符串长度的,其结果不包含终止符'\0'。例如:用strlen求字符串“abcdef”的长度为6。(strlen的返回值是无符号整型,这里我们模拟的函数返回类型为整型即可)
  • 因为strlen的结果不包含终止符'\0',所以我们可以把\0作为终止条件:从第一个字符开始读取,如果该位置不是\0,迭代继续,直到读取到\0时,停止读取。

普通方法(创建2个临时变量)

#include

int my_strlen(char ch[])//[]里写不写数字都一样
{
	int count = 0;//计数
	int i = 0;//每次迭代后位置往后挪一个字符
	while (ch[i] != '\0')
	{
		count++;
		i++;
	}
	return count;
}

int main()
{
	char ch[] = "abcdef";
	int num = my_strlen(ch);
	printf("%d\n", num);
	return 0;
}

指针方法(创建1个临时变量)

如果对指针有所了解的同学,也可以使用指针,更加方便。

#include

int my_strlen(char* ch)
{
	int count = 0;
	while (*(ch++) != '\0')//*ch为解引用
	{
		count++;
	}
	return count;
}

int main()
{
	char ch[] = "abcdef";
	int num = my_strlen(ch);
	printf("%d\n", num);
	return 0;
}

通过迭代可以顺利的实现strlen的功能,但是无论是普通的方法还是利用指针的方法,我们都创建了临时变量count用来计数,假设不使用临时变量呢?又如何进行?

递归方法

核心思想:

  • 递归的利用函数自己调用自己,以此来将一些复杂的事情简单化。
  • 设置my_strlen函数模拟实现,假设求字符串“abcedf”的长度,我们可以把字符串“abcdef”拆分成1+字符串“bcdef”,求字符串“abcedf”就变成了求1+"bcdef"的问题了,求"bcdef"的长度又可以调用我们设置的my_strlen函数。
  • 然后再将“bcdef”拆分成1+字符串“cdef”......直到剩最后一个字符‘f’时,把f拆分成1+‘\0’,当读取到‘\0’时,停止调用my_strlen函数,递归结束。

6.函数的递归_第1张图片

:递归中首先要递,我们将字符串一直拆分直到拆到\0这个动作就是递。

:递归中递完了才能归,当函数达到限制条件,无法再调用函数时,将本次函数的结果作为返回值,返回到上一个函数中,直到返回到第一次调用函数时。

递归中的递和归是相对应的,递和归的次数是一致的,比如某函数递归了6次,那么必定执行了6次递,6次归。

#include

int my_strlen(char* ch)
{
	if (*ch != 0)
		return 1 + my_strlen(ch+1);
	else
		return 0;
}
int main()
{
	char ch[] = "abcdef";
	int num = my_strlen(ch);
	printf("%d\n", num);
	return 0;
}

上述代码中,ch为字符串首地址元素,*ch为对ch存储的地址进行解引用,通过该地址找到字符串中的元素。

每次递归时地址向后+1,因为用char*的指针类型接收的ch的地址,所以这里+1表示为向后一个字节,如果是int*的指针类型,+1则是每次向后4个字节。(这里不要使用++ch,会彻底改变ch)。

当读到'\0'时,停止递归。

示例2:求斐波那契数

斐波那契数:1、1、2、3、5、8、13、21、34、56......从第三项开始,每一项的值等于前两项的和。

核心思想:

  • 当n=5时,第5个斐波那契数=第四个斐波那契数(n-1)+第三个斐波那契数(n-2)
  • 第四个斐波那契数=第三个斐波那契数(n-2)+第二个斐波那契数(n-3)
  • 第三个斐波那契数=第二个斐波那契数1+第一个斐波那契数1

6.函数的递归_第2张图片

这样就把求第五个斐波那契数,转换成了求n个第二个斐波那契数和求m个第一个斐波那契数的问题了。

#include
 
int fibo(int n)
{
	if (n > 2)
		return fibo(n - 1) + fibo(n - 2);
	else
		return 1;
}
 
int main()
{
	int n = 0;
	scanf("%d", &n);
	printf("%d\n", fibo(n));
	return 0;
}

示例3:求水仙花数

求所有的水仙花数,水仙花数是一个三位数,各位数字的立方和等于其本身。

例如:153=1^{3}+5^{3}+3^{3},故153是水仙花数。

核心思想:

例如:求125各位的立方

  • 首先,125可以拆分为12和5,求5的立方+求12各位的立方
  • 其次,12可以拆分为2和5,求2的立方+求1的各位的立方
  • 最后,1就它自己,求1的立方。

6.函数的递归_第3张图片

如何将125拆分成12和5呢?

这里将125/10,整数相除取商12;将125%10,取余数5。

#include
#include

int flo(int i)
{
	int sum = 0;
	if (i / 10 != 0)
	{
		sum = flo(i / 10);
	}
	return sum += pow((i % 10), 3);
}

int main()
{
	int i = 0;
	for (i = 100; i < 1000; i++)
	{
		if (flo(i) == i)
		{
			printf("%d是水仙花数\n", i);
		}
	}
	return 0;
}

 return sum += pow((i % 10), 3);可以理解为:

1、sum += pow((i % 10), 3);

2、return sum;

6.函数的递归_第4张图片

当1/10=0时,表达式为假,便不再调用函数,开始进行下一条语句,计算sum的值,然后return到上一个递归中,直到全部return完,递归结束。 

4.递归与迭代

递归与迭代的区别

在上述计算斐波那契数时,如果求第35个斐波那契数,程序很快便会给出结果;但是如果求第50个斐波那契数,程序则会运行的非常缓慢。

这是因为在求斐波那契数时,我们把所有的问题都转换成了求n个第一个斐波那契数和第m个斐波那契数。

6.函数的递归_第5张图片

仅仅是求第五个斐波那契数,便调用了9次函数自身。假如求第40个斐波那契数呢?会调用多少次函数?

这里设置一个全局变量count,对函数的调用次数进行计数。

int count = 0;
#include

int fibo(int n)
{
	count++;
	if (n > 2)
		return fibo(n - 1) + fibo(n - 2);
	else
		return 1;
}

6.函数的递归_第6张图片

求第40个斐波那契数共调用了2亿多次函数,每一次调用函数都需要在栈区中开辟一块空间,然而栈区的空间是有限的,当栈区满后,程序便进行不下去了。

当发生以上这种情况时,往往使用迭代效率会更加高,或者用static对象代替局部变量对象,以此释放栈区中的空间。

迭代与递归的对比
方式 优点 缺点
递归 形式清晰,思路明确 运行速度慢,栈区容易溢出
迭代 效率更高,速度更快 代码可读性差


用迭代的方式求斐波那契数

核心思想:

  • 通过创建临时变量的方式,存储前两项相加的结果,并且作为下一次迭代时的被加项目。
  • 为了防止数据溢出,这里用unsigned long long 来存储我们的结果。
#include
unsigned long long fibo(int n)
{
	unsigned long long sum=0;
	unsigned long long St=0;
	unsigned long long Sec=0;
	sum = Sec = 1;
	while (n > 2)
	{
		n -=1;
		St = Sec;
		Sec = sum;
		sum = St + Sec;
	}
	return sum;
}
int main()
{
	int n = 0;
	scanf("%d", &n);
	printf("第%d个斐波那契数是%llu\n",n,fibo(n));
	return 0;
}

6.函数的递归_第7张图片

 第50个斐波那契数是12586269025,将近126亿。

第100个斐波那契数是3736710778780434371,373京左右,unsigned long long存储的最大值为18446744073709551615,不过是1844京左右。然而这么大的数也只是迭代了98次得到的结果。

“不积跬步,无以至千里;不积小流,无以成江海。骐骥一跃,不能十步;驽马十驾,功在不舍。锲而舍之,朽木不折;锲而不舍,金石可镂。”C语言的学习也是如此,每天进步一点,今天比昨日博学一点,明日又比今天多学一分,相信我们最终都学有所成,前途无限。

5.递归练习题

这里整理了相关递归练习题,每道题都有详细讲解,有兴趣的同学可以尝试做一下。

并且在扫雷游戏中,相关功能的实现也用到了函数的递归。

你可能感兴趣的