递归,该怎么递怎么归啊?

函数的递归

1、什么是递归呢?

  • 递归,即程序(函数)自己调用自己的一种编程技巧,注意,这里的调用是需要在函数体中调用函数自身,而在调用函数语句中调用自身称为函数的链式访问。
  • 递归一般用来将一些大型复杂的问题层层在转换成为一个与原始问题相类似的规模较小的问题来进行求解。
  • 递归可以实现使用少量的代码来描述出一个复杂重复的计算过程,大大的减少了代码量。

下面我们看一个最简单的递归

int main()
{
	printf("hehe\n");
	main();
	return 0;
}

这个程序就是一个最简单的递归过程,在主函数main中循环调用main函数,无限的循环打印 ‘hehe‘ ,但是在我们运行的时候会看到这样的结果。代码停止运行了,这是为什么呢?

实际上是因为栈溢出的问题,计算机提供给我们的栈内存是有限制的,我们在递归的过程中不断开辟栈中的内存,当内存不足时就会停止执行。我们可以在调试中验证。当我们进行调试时会弹出Stack overflow警告,这就是栈溢出的警告。因此,在执行执行递归的时候,限制递归的执行条件就显得尤为重要。
Stack overflow警告

2 、递归的两个必要条件

  • 必须存在限制条件,当满足这个限制条件的时候,递归便不再继续。
  • 每次递归结束后,会越来越接近这个条件,使得递归是有限的。

2.1 练习1

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

递归过程分析

观察题目,我们会发现1234中的个位4在对10取模后会很容易得到,我们可以将1234分为123和4;而123中的3在对10取模后很容易得到,我们可以将123分为12和3,以此类推。

print()递归函数的执行过程如下:

函数print(1234)将1234分为123和4,先将123传入print()函数中;
得到函数print(123),将123分为12和3,将12传入print()函数中;
得到函数print(12),将12分为1和2,将1传入print()函数中;
得到函数print(1),模10打印1;
回到函数print(12),模10打印2;
回到函数print(123),模10打印3;
回到函数print(1234),模10打印4;
程序运行结束!!!

代码演示
void print(int n)
{
	if (n > 9)
	{
		print(n / 10);
	}
	printf("%d ", n%10);
}
int main()
{
	unsigned int num = 1234;
	print(num);
	return 0;
}
递归过程画图演示

接下来再用画图的方式来执行一遍递归的过程。在这里插入图片描述

2.2 练习2

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

分析递归过程

这个递归的思想与上一个练习相似。当指针p所指的字符不为’\0‘则将指针p向后移动一个字符,知道指向’\0’时return 0。依次向上一个调用的函数返回0;1;2;3;4;然后返回到主函数,程序执行结束。

代码演示
非递归的方法
int strlen(char* p)
{
	int count = 0;
	while (*p != '\0')
	{
		count++;
		p++;
	}
	return count;
}
int main()
{
	char arr[] ="abcd";
	int len = strlen(arr);
	printf("%d\n", len);
	return 0;
}
递归的方法
//递归的方法
int strlen(char* p)
{
	if (*p == '\0')
	{
		return 0;
	}
	else
	{
		return 1 + strlen(p+1);
		//注意这里不能写为p++,这样会先使用p再++,会导致指针p一直指着字符a,因此会陷入死递归。可以写为++p。
	}
}
int main()
{
	char arr[] ="abcd";
	int len = strlen(arr);
	printf("%d\n", len);
	return 0;
}
递归过程画图演示

接下来再用画图的方式来执行一遍递归的过程。
在这里插入图片描述

3 递归与迭代

迭代,我们之前学习的循环就是一种迭代。

3.1 练习3

求n的阶乘。

迭代的方法

迭代,即循环。就是将重复的操作循环进行执行。

//迭代的方法
int main()
{
	int sum = 1;
	int n = 0;
	scanf("%d", &n);
	for (int i = 1; i <= n; i++)
	{
		sum = sum * i;
	}
	printf("%d\n", sum);
	return 0;
}
递归的方法

求n的阶乘就是:n*(n-1)*(n-2)…21.因此,我们可以将他写为数学公式:

n = 1时 ,return 1;
n > 1时,retrurn n * factorial(n - 1);

int factorial(int n)
{
	if (n == 1)
	{
		return 1;//这里的(n == 1)相当于限制递归的条件,让递归有一个终点。
	}
	else
	{
		return n * factorial(n - 1);
	}
}
int main()
{
	int n = 0;
	scanf("%d", &n);
	int sum = factorial(n);
	printf("%d\n", sum);
	return 0;
}

我们可以看到,不管是使用迭代的方法还是递归的方法,都可以实现n的阶乘,迭代主要使用的是循环,而递归使用的是函数调用。

3.2 练习4

求第n个斐波那契数。(不考虑溢出)

递归的方法
分析:

我们可以观察斐波那契数列1,1,2,3,5,8,13,21,34,55…从第三个数开始是前两个数之和。而只有前两个数是 1.因此,我们可以将他写为数学公式:

n <= 2时 ,return 1;
n > 2时,retrurn fib(n - 1) + fib(n - 2);

这样我们的递归过程就出来了。

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\n", ret);
	return 0;
}

在我们执行完代码后会发现,当我们输入的n较小时运行时间还是比较短的,但当我们输入40,50时就会有一个很明显的等待时间。这是因为计算机进行了庞大且重复的运算。因此,在解决斐波那契数的问题时,递归的方法就不再适合。

迭代的方法
int fib(int n)
{
	int i = 0;
	int old_num = 1;//old_num指的是当前数的前一个数
	int old_old_num = 1;//old_old_num指的是当前数的前前一个数
	int ret = 0;
	for (i = 0; i < n - 2; i++)
	{
		ret = old_num + old_old_num;
		old_old_num = old_num;
		old_num = ret;
	}
	return ret;
}
int main()
{
	int n = 0;
	scanf("%d", &n);
	int ret = fib(n);
	printf("%d\n", ret);
	return 0;
}

使用迭代的方法我们可以明显的感受到在n输入为50的时候计算机的执行速度非常的快,尽管结果不对(那是因为数字太大超过二进制的最大值了),但明显要比递归的方法效率更高。因此,在解决斐波那契数的问题时,迭代是比较好的方法。

什么时候使用递归,什么时候用非递归呢?

  • 很多问题由于使用迭代的方法不能很清晰的展现出解决问题的过程,因此可以使用递归的思想来对它的过程进行解释。只是因为递归比非递归的形式更加清晰。但是往往这些问题的迭代实现效率会更高一些。
  • 在解决问题的过程中,只要使用递归写完之后没有明显的缺陷,那么就使用递归。若是有明显的缺陷时,则迭代再麻烦也要使用迭代。
  • 14
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 13
    评论
评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值