递推与递归算法(兔子问题 C语言)

递推

递推思想跟枚举思想一样,都是接近人类思维方式的思想,甚至在实际生活具有比枚举思想更多的应用场景。人脑在遇到未知的问题时,大多数人第一直觉都会从积累的「先验知识」出发,试图从「已知」推导「未知」,从而解决问题,说服自己。

事实上,这就是一种递推的算法思想。递推思想的核心就是从已知条件出发,逐步推算出问题的解。实现方式很像是初高中时我们的数学考卷上一连串的「因为」「所以」。那个时候还是用三个点来表示的。

而对于计算机而言,复杂的推导其实很难实现。计算机擅长的是执行高密度重复性高的工作,对于随机性高变化多端的问题反而不好计算。相比之下,人脑在对不同维度的问题进行推导时具有更高的自由度。

比方说,人脑可以很容易的从「太阳从东边升起」推出「太阳从西边落下」,然后大致推出「现在的时间」。但是对于计算机而言并没有那么容易,你可能需要设置一系列的限制条件,才能避免计算机推出「太阳/月亮/星星」从「南/北/东边」「落下/飞走/掉落」的可能性。

我说这个例子的用意是在说明,计算机在运用递推思想时,大多都是重复性的推理。比方说,从「今天是1号」推出「明天是2号」。这种推理的结构十分类似,往往可以通过继而往复的推理就可以得到最终的解。

递推思想用图解来表示可以参见下图。每一次推导的结果可以作为下一次推导的的开始,这似乎跟迭代、递归的思想有点类似,不过递推的范畴要更广一些。
在这里插入图片描述

应用

兔子问题:有一对兔子,从出生后第3个月起每个月都生一对兔子,小兔子长到第三个月后每个月又生一对兔子假如兔子都不死,问每个月的兔子总数为多少?

:通过题目,我们可以依次列出前几个月兔子的情况,再找到规律解决问题

月份123456789
11011235813
2010112358
30011235813
total112358132134

通过上述列表我们不难发现,d当计算这个月的兔子时。处在3月的兔子等于上一个月处在3月的兔子 + 上一个月处在2月的兔子;处在2月的兔子就等于上一个月处在1月的兔子;处在1月的兔子就等于处在3月的兔子的个数。

代码:

//有一对兔子,从出生后第3个月起每个月都生一对兔子,小兔子长到第三个月后每个月又生一对兔子
//假如兔子都不死,问每个月的兔子总数为多少?

size_t Rabbit(int count)
{
	if (count == 0)
	{
		return 0;
	}
	if (count == 1 || count == 2)
	{
		return 1;
	}

	int one = 1;
	int two = 0;
	int three = 0;
	size_t total = 0;
	while (--count)
	{
		three += two;
		two = one;
		one = three;
	}
	total = one + two + three;

	return total;
}

int main()
{
	int count = 9;
	size_t res = Rabbit(count);
	printf("%d个月共可以产%u对兔子\n", count, res);

	return 0;
}

运行截图:
在这里插入图片描述

递归

完递推,就不得不说说它的兄弟思想——递归算法。二者同样都带有一个「递」字,可以看出二者还是具有一定的相似性的。「递」的理解可以是逐次、逐步。在递推中,是逐次对问题进行推导直到获得最终解。而在递归中,则是逐次回归迭代,直到跳出回归。

递归算法实际上是把问题转化成规模更小的同类子问题,先解决子问题,再通过相同的求解过程逐步解决更高层次的问题,最终获得最终的解。所以相较于递推而言,递归算法的范畴更小,要求子问题跟父问题的结构相同。而递推思想从概念上并没有这样的约束。

用一句话来形容递归算法的实现,就是在函数或者子过程的内部,直接或间接的调用自己算法。所以在实现的过程中,最重要的是确定递归过程终止的条件,也就是迭代过程跳出的条件判断。否则,程序会在自我调用中无限循环,最终导致内存溢出而崩溃。

递归算法的图解可如下图。很明显,递归思想其实就是一个套娃过程。一般官方都是严禁套娃行为的。所以在使用时一定要明确「套娃」举动的停止条件,及时止损。
在这里插入图片描述

应用

之所以把这两个算法放在一起,是因为这个两个算法非常类似,这里我们还是通过兔子问题来加深理解

月份123456789
11011235813
2010112358
30011235813
total112358132134

通过这个表格我们还发现一个规律,就是从第3个月起,兔子的总数等于前两个月的兔子总数之和。其实,这就是著名的斐波那契数列

代码:

size_t Rabbit(int count)
{
	if (count == 1 || count == 2)
	{
		return 1;
	}
	return Rabbit(count - 1) + Rabbit(count - 2);
}

int main()
{
	int count = 9;
	size_t res = Rabbit(count);
	printf("%d个月共可以产%u对兔子\n", count, res);

	return 0;
}

运行结果:
在这里插入图片描述

问题

以上两个代码,第二个代码看起来简洁,但是它的缺点也非常明显,那就是会浪费内存空间,调用一次Rabbit函数,就会调用2次Rabbit函数,而这两个函数又再分别调用2次。我们不难发现调用的次数呈指数增长。读者可以尝试用这两个代码分别从第35个月开始依次增加一个月,看运行的速度以及结果。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

WhiteShirtI

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值