概述
在介绍递归算法时,递归发挥了两个作用,一个是递
,另一个是归
。递
的意思是不断向下延伸,归
的意思是返回上一个步骤。这两个操作为我们提供了无限的可能。其中最重要的一个应用就是尝试
。关于尝试的概念,使用迷宫是好理解的。当我们身处一个迷宫中,我们可以做的事只有一个,不断的尝试,并在尝试过的位置做好标记。走过的路,我们不会再重复进行,这让我们可以快速通过迷宫。
递归回溯的功能正如身处迷宫中的我们——不断尝试,并对走过的路进行标记。我们以一个简单的实例来说明问题。
小明跳楼梯,小明每次可以跳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
的值将变成2
。arr[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+1
,1+2
,2+1
。当按照规定时,变成2
种,也就是1+1+1
和1+2
,当然你也可以看做1+1+1
和2+1
这两种。这是可以的。为了方便表达,我们常常按照从小到大排列查看,这样有利于找到对应的规律。
那么,3
阶楼梯的跳法有2
种,分别为1+1+1
和1+2
。4
阶楼梯不按规定查看的话是5
种,分别是1+1+1+1
,1+1+2
,1+2+1
,2+1+1
,2+2
。按照规定来看的话是3
种,分别是1+1+1+1
,1+1+2
,2+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; //输出种类
}
最后
以上就是曾经鼠标为你收集整理的算法之递归回溯(一)的全部内容,希望文章能够帮你解决算法之递归回溯(一)所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复