汉诺塔:有三根相邻的柱子,标号为A,B,C,A柱子上从下到上按金字塔状叠放着n个不同大小的圆盘,要把所有盘子一个一个移动到柱子B上,并且每次移动同一根柱子上都不能出现大盘子在小盘子上方,应该怎么移动?
汉诺塔是个非常经典的问题,讲递归时应该都会讲到它。如果我们没有递归的先验知识, 直接去解答这道题,常常会觉得不知道如何下手。用递归却可以非常优美地解决这个问题。
使用递归的一个关键就是,我们先定义一个函数,不用急着去实现它, 但要明确它的功能。
对于汉诺塔问题,我们定义如下函数原型:
void hanoi(int n, char src, char bri, char dst);
我们先不去关心它是怎么实现的,而是明确它的功能是:
将n个圆盘从柱子src移动到柱子dst,其中可以借助柱子bri(bridge)。
注:n个圆盘从上到下依次的标号依次为1到n,表示圆盘从小到大。
移动的过程中,不允许大圆盘放在小圆盘的上面。
OK,既然要用到递归,当然是在这个函数中还是用到这个函数本身, 也就是说,我们完成这个任务中的步骤还会用到hanoi这个操作,只是参数可能不一样了。 我们定义一组元组来表示三根柱子的状态:(src上的圆盘,bri上的圆盘,dst上的圆盘) 初始状态是:(1~n, 0, 0)表示src上有1到n共n个圆盘,另外两个柱子上没有圆盘。 目标状态是:(0, 0, 1~n)表示dst上有1到n共n个圆盘,另外两个柱子上没有圆盘。 由于最大的圆盘n最后是放在dst的最下面,且大圆盘是不能放在小圆盘上面的, 所以,一定存在这样一个中间状态:(n,
1~n-1, 0),这样才能把最大的圆盘n 移动到dst的最下面。这时候,有人就会问,你怎么就想到这个中间状态而不是其它呢? 好问题。因为,我现在手头上的工具(可用的函数)只有hanoi, 那我自然要想办法创造可以使用这个函数的情景,而不是其它情景。
初始状态是:(1~n, 0, 0)
中间状态是:(n, 1~n-1, 0)
从初始状态到中间状态使用操作hanoi(n-1, src, dst, bri)就可以到达了。即把n-1 个圆盘从src移动到bri,中间可以借助柱子dst。
接下来就是将圆盘n从src移动到dst了,这个可以直接输出:
cout<<"Move disk "<<n<<" from "<<src<<" to "<<dst<<endl;
这个操作后得到的状态是:
(0, 1~n-1, n)
然后再利用hanoi函数,将n-1个圆盘从bri移动到dst,中间可借助柱子src, hanoi(n-1, bri, src, dst),操作后得到最终状态:
(0, 0, 1~n)
这些操作合起来就三行代码:
hanoi(n-1, src, dst, bri);
cout<<"Move disk "<<n<<" from "<<src<<" to "<<dst<<endl;
hanoi(n-1, bri, src, dst);
最后,我们还需要递归停止条件。什么时候递归结束呢?当n等于1时,既只有一个圆盘时, 直接把它从src移动到dst即可:
if(n==1)
{
cout<<"Move disk "<<n<<" from "<<src<<" to "<<dst<<endl;
}
void hanoi(int vN, char vSrc, char vBriage, char vDest) { if (vN == 1) { std::cout << "move the dist "<< vN << " from " << vSrc << " to " << vDest << std::endl; return; } hanoi(vN-1, vSrc, vDest, vBriage); std::cout << "move the dist "<< vN << " from " << vSrc << " to " << vDest << std::endl; hanoi(vN-1, vBriage, vSrc, vDest); }