算法之递归回溯(一)

本文通过小明跳楼梯的例子详细解释了递归算法的工作原理,探讨了如何利用递归解决实际问题。在跳楼梯问题中,通过递归函数模拟不同跳法,并通过回溯实现所有可能的路径。最后,文章展示了如何通过递归算法找出所有合法的跳法,并计算出总共有多少种不同的跳法。
摘要由CSDN通过智能技术生成

在介绍递归算法时,递归发挥了两个作用,一个是,另一个是的意思是不断向下延伸,的意思是返回上一个步骤。这两个操作为我们提供了无限的可能。其中最重要的一个应用就是尝试。关于尝试的概念,使用迷宫是好理解的。当我们身处一个迷宫中,我们可以做的事只有一个,不断的尝试,并在尝试过的位置做好标记。走过的路,我们不会再重复进行,这让我们可以快速通过迷宫。
递归回溯的功能正如身处迷宫中的我们——不断尝试,并对走过的路进行标记。我们以一个简单的实例来说明问题。

小明跳楼梯,小明每次可以跳1格或2格。问小明跳n阶楼梯一共有多少种,并且每一种是如何跳的呢?

对于一共有多少种这个问题,我们已经解答过了,它符合斐波那契数列。我们可以很快地写出代码。但对于每一种如何跳的?对于现在的我们来说,是棘手的。但递归给我们提供了思路。
我们了解一点,小明跳楼梯,每次要么跳1格,要么2格。总有一种他会选择。这是我们需要使用到的地方。我们如何使用呢?我们尝试!我们假设他跳1格,那么这一次跳过1格,下一次就是针对剩下的楼梯n-1跳了。这时,他仍然可以选择跳1格或2格。每次他都可以。那么我们可以使用一个循环,让他把每一种跳法都跳一遍好了。也就是说,第一次跳的时候,我们先让他跳1格,一直跳到最高层n阶。然后在把最后一次跳的楼梯数变成2格,就这样一遍一遍地尝试。最终给出我们的结果。对于结果,我们可以存放在一个全局变量的数组中。这是可行的。我们在这里使用伪代码简单描述一下过程。

//这是伪代码
int arr[100] = {};	//用来存放每一次的跳法。跳1格或跳2格,其中保存1或者2
//定义一个递归函数
//n 表示现在小明需要跳的楼梯总数为n
//index 用来存放第index次跳楼梯的方式	index 从1开始
void f(int n, int index)
{
	if(n < 0)
		return;		//楼梯数不能为负数,负数情况直接排除
	if(n == 0)	//小明跳的楼梯数为0,说明没有楼梯跳或者已经跳到最高层了
	{//直接结束,结束之前可以输出跳的过程
		//输出过程
		for(int i = 1; i < index; ++i)
			cout << arr[i] << " ";	//从第一次跳,到index 前一次跳的楼梯数
		cout << endl;
		return;	//没有楼梯数跳的话,直接结束程序
	}
	
	//我们并不知道小明这次跳1格还是2格,那么我们将1格或2格都试一遍
	for(int i = 1; i <= 2; ++i)
	{
		arr[index] = i;	//保存小明跳楼梯的方式
		//小明这次跳了i格,那么只剩下n-i格楼梯了
		//下一次跳的情况保存在index + 1的位置
		f(n - i, index + 1);
	}
}

int main()
{
	f(3, 1);	//假设楼梯数为3, 1 为第一次跳
}
已经使用程序描述清楚了,但程序具体的细节我们并不了解。这是不利的。我们尝试进行分析。
1、主函数中调用 f(3,1)。楼梯数为 3,第 1次跳楼梯。很显然, n的值为 3,并不为 0。因此,程序会直接进入 for循环。循环语句的 i开始初始化, i的值为 1。也就是说第 1次跳的是 1格。我们将 1格存放进 arr[index]的位置, index此时也为 1,我们得到 arr[1]=1。接下来程序会进入递归 f(3-1,2)
2、很显然, n此时是 2并不等于 0,程序会继续进入循环。循环中的 i被重新定义。 i的值为 1。也就是说,小明第 2次也跳了 1格。我们将 1存放进 arr[2]的位置。接着再次进入循环。 f(2-1,3)被调用。
3、 2-1的结果也没有得到 0。程序会继续进入循环。然而,循环还是从 1开始的。因此,和之前一样,程序会将 1保存到 arr[3]的位置,并再次以 f(1-1,4)的形式进入下一次函数调用。
4、终于在这一次, n的值变成了 0。程序开始输出 arr中的内容,我们知道小明实际跳的次数是 3次,而 index是数字 4,我们在循环中使用小于 index来表示没有取到 4。输出结束之后,程序执行 return表明程序结束。
5、程序结束表明 f(1-1,4)的函数调用结束了。但这个函数处于一个循环中,循环的 i此时值为 1。下一次循环会照常进行, i的值为 2。执行下面这行语句 arr[index] = i,这句写出来是有原因的。我们需要注意此时 index的值。这个循环处于 f(2-1,3这个函数中,因此 index的值为 3。所以赋值语句的具体表达是 arr[3] = 2。程序执行下一句,调用 f(1-2,4)
6、进入这个函数之后,程序就会发现, n的值为 -1,小于 0,程序直接结束,而什么也不做。它没办法做任何事情,他跳楼梯不能跳负数格的。
7、那么 f(1-2,4)的函数快速结束。而这个函数所处的循环也到头了。 i等于 3时,循环跳出。这也就意味着 f(2-1,3)这个函数的结束。我们需要了解到的是, f(2-1,3)这个函数是在 f(3-1,2)这个函数中调用的。因此,程序会回到 f(3-1,2)这个函数的循环中。此时 i的值还是 1,下一次循环, i的值将变成 2arr[index] = i会正常保存 2这个数字,并作为小明这次跳楼梯的格数。进行下一步, f(2-2,3)的函数调用开始了。
8、 n的值为 0。程序会输出结果。

程序按照这样的步骤一步一步进行着,直到最开始的那个循环也结束。这样函数就完成它的使命。这是给出跳楼梯跳法的程序,那么既然每一次如何跳的都已经知晓了,那么可以直接通过这个代码获得有多少种吗?当然是没问题的。我们只需要添加一个全局变量。当n的值为0时,也就是一种跳法完成了,我们也就可以将跳法增加1了。具体的代码描述如下:

int arr[100] = {};
int cnt = 0;
void f(int n, int index)
{
	if(n < 0)
		return;
	if(n == 0)
	{
		++cnt;	//此时,跳法的数量增加1
		for(int i = 1; i < index; ++i)
			cout << arr[i] << " ";
		cout << endl;
		return;
	}
	for(int i = 1; i <= 2; ++i)
	{
		arr[index] = i;
		f(n - i, index + 1);
	}
}

从程序结果我们可以了解到,当小明跳3阶楼梯时,有一种方式是先跳1格,然后在跳2格。还有一种是先跳2格,再跳1格。如果我们把先跳1格,再跳2格的方式记作1+2,把先跳2格,再跳1格的方式记作2+1,并规定这两种方式为一种时,一共有多种跳法呢?
我们知道,楼梯数为3时,按照没有规定时的状态来看,答案为3种,分别为1+1+11+22+1。当按照规定时,变成2种,也就是1+1+11+2,当然你也可以看做1+1+12+1这两种。这是可以的。为了方便表达,我们常常按照从小到大排列查看,这样有利于找到对应的规律。
那么,3阶楼梯的跳法有2种,分别为1+1+11+24阶楼梯不按规定查看的话是5种,分别是1+1+1+11+1+21+2+12+1+12+2。按照规定来看的话是3种,分别是1+1+1+11+1+22+2。从这两种情况来看,我们保证的是前一次跳的楼梯数需要小于或等于后一次的楼梯数,但至少需要跳1格,至多只能跳2格。因此,我们可以将程序简单修改一下。

int arr[100] = {};
int cnt = 0;
void f(int n, int index)
{
	if(n < 0)
		return;
	if(n == 0)
	{
		++cnt;	//此时,跳法的数量增加1
		for(int i = 1; i < index; ++i)
			cout << arr[i] << " ";
		cout << endl;
		return;
	}
	//i 的值需要从前一个跳法中得到
	//第一次跳时,需要确保从1格开始跳,因此需要保证arr[index - 1] = 1;
	//第一次跳时,index = 1
	//也就是说,arr[1 - 1] = 1;
	for(int i = arr[index - 1]; i <= 2; ++i)
	{
		arr[index] = i;
		f(n - i, index + 1);
	}
}
int main()
{
	arr[0] = 1;	//保证第一次跳的时候从1格开始
	f(5,1);		//假设跳的楼梯数为5
	cout << cnt << endl;	//输出种类
}
	
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值