小bai笔记特别篇:汉诺塔和青蛙跳台阶(含变态青蛙)问题(函数和递归进阶篇)

前言:hello大家好,我是小白阿g今天更一期函数和递归的进阶特别篇,主要就是讲解一下这两个经典的问题,带大家更加深入的感受一下递归思想,主要还是讲解思路,还是那句话,我的努力哪怕为大家理解培养递归思想起到一点点作用,那也是值得的,话不多说,直奔主题。

1汉诺塔问题

关于汉诺塔问题可能有人没有听说过哈,简单给大家在网页上粘贴过来一个描述,如图:

在这里插入图片描述

简单的来说就是三个柱子ABC,A柱子上从小到大有一摞盘子,每次挪动一个,把盘子从A都挪到C上去,规则就是不能出现大盘子在小盘子上边的情况。

可能大家一开始看见这个题会觉得比较难,没有思路,现在我给大家重点讲解思路,依旧是递归大事化小思想,大问题咱们先不看,我们知道,如果A上只有一个盘子,是不是只需要一步就可以把它放到C上去,那么显然这就是大问题的最小子问题,即把大问题递减到A上只有1个盘子。那么假如有2个盘子,我们的挪法也是很简单的,那就是先把第一个放到B上去,这下A上就只有一个盘子了,也就是我们的最小子问题,我们把它放到C上就可以了,然后再把上的那个盘子放到C上去就完成了任务。那么我们现在基本就有了完整思路,那就是挪动n个盘子,我们可以先把n-1个盘子挪到B柱子上,这样A上就只剩1个盘子了,我们很容易就可以挪过去,挪到C之后,由于挪过去的是最大的盘子,剩下的所有盘子都是可以放在上面的,也就相当于剩余了一堆碟子在B上,A和C都是空的,那么就变成了n-1个盘子挪动的汉诺塔问题,至此我们就把n个盘子的汉诺塔问题变成了第一步n-1个盘子从A挪到B,第二步A上剩的一个盘子挪到C上,第三步再把n-1个盘子从B挪到C上的汉诺塔问题。这样递减下去,肯定会变成我们的最小子问题:把盘子从A放到C上去,然后再逐级归还,这样问题就解决了。再来完整捋一下思路:n个盘子移动的汉诺塔问题,把n分解成最大的盘子和n-1个盘子的子问题,最大的盘子我们可以直接挪动,剩下的n-1个盘子我们可以调用自身,即规模减小的汉诺塔问题,这就是递归,C语言实现例如下代码:

//定义全局变量count,统计挪动次数
int count = 0;
//定义一个“挪动”函数,形参是起始位置和结束位置
//即传给函数两个实参(柱子名,例如A,B),move函数在屏幕上打印 A-->B
void move(char Star, char End)
{
	printf("%c --> %c\n", Star, End);
	//每次move被调用,移动次数count自加1
	count++;
}

//定义汉诺塔函数,形参为挪动盘子数x,开始位置Star,中专位置Mid,目的位置End
//实参为:   数值,A,B,C
void Hanoi(int x, char Star, char Mid, char End)
{
	if (x == 1)
	{
		move(Star, End);
	}
	else
	{
		//先将x-1个盘子从A柱通过C柱挪到B柱
		Hanoi(x-1, Star, End, Mid);
		//A上剩余一个盘子,把它从A移动到C
		move(Star, End);
		//把B上剩余的n-1个盘子通过A挪到C上
		Hanoi(x-1, Mid, Star, End);
	}
}

int main()
{
	//求解三个盘子挪动问题
	printf("当前要挪动4个盘子,挪动方法为:\n");
	Hanoi(4, 'A', 'B', 'C');
	printf("挪动了%d次\n", count);
	return 0;
}

运行结果:

         到这里如果大家自己写了代码,并且执行了几次的话,那么就不难发现,其实n个盘子的挪动次数就是(2^n)-1。这里插一段关于汉诺塔的传说,挺有意思的,这个问题的原版是:

         通过我们上边的结论,我们可以知道,64个盘子一共需要挪动2^64-1次,这是一个多大的数字呢,如果僧侣一秒挪一次,不停不休,不挪错,换算时间为年的话是:

         当然这就是一个传说啦,跟我们是没什么关系的,我们主要从汉诺塔问题体会学习递归思想就好了。

2青蛙跳台阶问题:

        这个问题比较简单,其实本质就是斐波那契数列,当然我们要做的是理解,下次在遇到能知道这可以用递归思想解决,这才是我们的目的。

        问题是,有一只青蛙面前有无数级台阶,它可以一次跳一个台阶,也可以一次跳两个台阶,问青蛙跳到第n级台阶有几种跳法。

        好,问题描述清楚,我们开始大事化小,由于这个青蛙跳一次只能跳1到2个,所以它跳到第n个台阶只有两种可能,从n-1跳过来或者从n-2跳过来。这样又把问题递减了,看过斐波那契数列的小伙伴到这里应该也就懂了,这不就是一个数等于它前两个数之和吗,没错,我们说青蛙跳台阶问题本质就是斐波那契数列,现在我相信大家对于递归的魅力略懂一二了,他把青蛙和数学联系在了一起,而且是紧紧联系,本质相同,这就是递归思想。那么代码就很简单了,相信大家都会写了,我这里呢也在提供一份,供大家参考,例如下代码:

int Jump(int x)
{
	if (x == 1)
	{
		return 1;
	}
	else if (x == 2)
	{
		return 2;
	}
	else
	{
		return Jump(x-1)+Jump(x-2);
	}
}

int main()
{
	int ret = Jump(40);
	printf("有%d种跳法\n", ret);
	return 0;
}

         不过有一点大家略微注意,这个比斐波那契数列少一个1,因为青蛙跳第二级台阶可以直接跳上去或者先跳台阶1再跳台阶2,也就是数列第二项就是2了,后面的不变。当然看了我上期内容的小伙伴应该知道,递归解决斐波那契是垃圾的算法,不过我们学的是思想,学会用递归解决问题,能从问题中找出递归就可以了,至于这个算法并不适合我们可以再想别的办法嘛,相信看来上期的内容的话应该很容易就写出循环方法啦,这里也附给大家,仅供参考,例如下代码:

int Jump(int x)
{
	int a = 1;
	int b = 2;
	int c = 1;
	if (x == 1)
	{
		return a;
	}
	if (x == 2)
	{
		return b;
	}
	while (x > 2)
	{
		c = a+b;
		a = b;
		b = c;
		x--;
	}
	return c;
}

         当然啊,青蛙跳台阶问题本身是比较简单滴,之所以给大家在进阶篇中讲,其一是因为这是非常经典的一个问题,是大厂笔试曾经出过的题目;其二就是作为引子,引出我们的变态青蛙问题。问题是这样滴:

        话说这个青蛙天天在这跳台阶,哎,有一天它修仙得道了,成为了一只无比强大的青蛙,它现在牛起来了,它一次可以跳任意个台阶,这是我们进阶内容哈,现在我们来思考:

        其实这个题看起来复杂,但是只要想通了,也是比较简单的。还是大事化小考虑有没有能缩小规模的子问题。比如青蛙跳到第n阶,那么它可能从那跳过来呢?显然由于现在青蛙比较牛,他可能是从第1级第2级...一直到第n-1级中任意台阶跳过来的,也就是说Jump(n)不在等于Jump(n-1)+Jump(n-2),它现在的关系是Jump(n)=Jump(1)+Jump(2)+....+Jump(n-2)+Jump(n-1)+1(直接一下跳上来)。这样我们把问题理解了之后,很容易就搞明白了关系,还是一组数列,只不过现在数列的每一个数都是它前面所有项的和再加1。相信大家都能做出循环方法,那么递归能解吗,怎么解呢。

        我们说这个变态青蛙问题是可以用递归解决的,思路如下:每个n级的跳法都是它前面所有的方法之和加1(直接一下跳上来),那么我们可以让结果为1+(调用自己传参数为n-1),这样调用自己传参数为n-1又会变成1+(调用自己传参数为n-2),逻辑漏洞来了,重点重点重点:这样我们写出来一个表达式:Jump(n)=1+Jump(n-1),看上去很对,但是大家一定要好好注意这里的逻辑思维,Jump(n)=Jump(1)+Jump(2)+....+Jump(n-2)+Jump(n-1)+1,而不是Jump(n)=1+Jump(n-1),算出来的Jump(n)什么都不是,这样的逻辑思维求出来的Jump(n)就等于n。正确的表达式是把每一次的结果都加起来,所以我们定义一个全局变量:sum,函数递归表达式为sum += Jump(n-1),完整例如下代码:

int sum = 1;
int Jump(int x)
{
	if (x == 1)
	{
		return 1;
	}
	sum += Jump(x-1);
	return sum;
}

 循环的代码也分享给大家:

int Jump(int x)
{
	int a = 1;
	while (x > 0)
	{
		a += a;
		x--;
	}
	return a;
}

3好了,递归的进阶篇就讲到这里啦,重点就是表态青蛙哪里的递归思考过程,一定要好好理解,希望我的这篇博客可以帮到大家,ok,这期的内容到此结束,如果觉得对你有帮助欢迎点赞转发评论区交流,关注小白阿g,让小白不再白学,亲爱的小伙伴们下期见。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值