目录
汉诺塔
1 简介
汉诺塔(Tower of Hanoi),又称河内塔,是一个源于印度古老传说的益智玩具。大梵天创造世界的时候做了三根金刚石柱子,在一根柱子上从下往上按照大小顺序摞着64片黄金圆盘。大梵天命令婆罗门把圆盘从下面开始按大小顺序重新摆放在另一根柱子上。并且规定,在小圆盘上不能放大圆盘,在三根柱子之间一次只能移动一个圆盘。
法国数学家爱德华·卢卡斯曾编写过一个印度的古老传说:在世界中心贝拿勒斯(在印度北部)的圣庙里,一块黄铜板上插着三根宝石针。印度教的主神梵天在创造世界的时候,在其中一根针上从下到上地穿好了由大到小的64片金片,这就是所谓的汉诺塔。不论白天黑夜,总有一个僧侣在按照下面的法则移动这些金片:一次只移动一片,不管在哪根针上,小片必须在大片上面。僧侣们预言,当所有的金片都从梵天穿好的那根针上移到另外一根针上时,世界就将在一声霹雳中消灭,而梵塔、庙宇和众生也都将同归于尽。
不管这个传说的可信度有多大,如果考虑一下把64片金片,由一根针上移到另一根针上,并且始终保持上小下大的顺序。这需要多少次移动呢?这里需要递归的方法。假设有n片,移动次数是f(n).显然f(1)=1,f(2)=3,f(3)=7,且f(k+1)=2*f(k)+1。此后不难证明f(n)=2^n-1。n=64时,
假如每秒钟一次,共需多长时间呢?一个平年365天有31536000 秒,闰年366天有31622400秒,平均每年31556952秒,计算一下:
18446744073709551615秒
这表明移完这些金片需要5845.54亿年以上,而地球存在至今不过45亿年,太阳系的预期寿命据说也就是数百亿年。真的过了5845.54亿年,不说太阳系和银河系,至少地球上的一切生命,连同梵塔、庙宇等,都早已经灰飞烟灭。
图解:
2 代码思路
2.1 对于次数的理解
当n=1时,把所有圆盘从A挪到另一个柱需要1次
当n=2时,相当于n=1的基础上再把一个圆盘放到A,那么把新圆盘放到原圆盘的下面需要2次,所以总共需要1+2=3次
当n=3时,相当于在n=2的基础上再把一个圆盘放到A,那么把新圆盘放到原所有圆盘的下面需要4次,所以总共需要1+2+4=7次
当n=4时,相当于在n=3的基础上再把一个圆盘放到A,那么把新圆盘放到原所有圆盘的下面需要8次,所以总共需要1+2+4+8=15次
如此循环,构成递归,递归边界是n=1
2.2 对于移动的理解
先将n-1个圆盘从A柱移动到B柱上,然后将A柱上最后一个圆盘移动到C柱上,最后再把B柱上的n-1个圆盘移动到C柱上。如下图所示:
当n=1时:
1.将A柱上最后一个圆盘移动到C柱上(A →C)
当n=2时:
1.将1个圆盘从A柱移动到B柱上,重复n=1时的步骤,只不过是将那1个圆盘(从A借助于B移动到C)改为(从A借助于C移动到B)
2.将A柱上最后一个圆盘移动到C柱上(A →C)
3.将B柱上的1个圆盘移动到C柱上。重复n=1时的步骤,只不过是将那个圆盘(从A借助于B移动到C)改为(从B借助于A移动到C)
当n=3时:
1.将2个圆盘从A柱移动到B柱上。重复n=2时的步骤,只不过是将那2个圆盘(从A借助于B移动到C)改为(从A借助于C移动到B)
2.将A柱上最后一个圆盘移动到C柱上(A →C)
3.将B柱上的2个圆盘移动到C柱上。重复n=2时的步骤,只不过是将那2个圆盘(从A借助于B移动到C)改为(从B借助于A移动到C)
当n=4时:
1.将3个圆盘从A柱移动到B柱上。重复n=3时的步骤,只不过是将那3个圆盘(从A借助于B移动到C)改为(从A借助于C移动到B)
2.将A柱上最后一个圆盘移动到C柱上(A →C)
3.将B柱上的3个圆盘移动到C柱上。重复n=3时的步骤,只不过是将那3个圆盘(从A借助于B移动到C)改为(从B借助于A移动到C)
当n时:
1.将n-1个圆盘从A柱移动到B柱上。重复n=n-1时的步骤,只不过是将那n-1个圆盘(从A借助于B移动到C)改为(从A借助于C移动到B)
2.将A柱上最后一个圆盘移动到C柱上(A →C)
3.将B柱上的n-1个圆盘移动到C柱上。重复n=n-1时的步骤,只不过是将那n-1个圆盘(从A借助于B移动到C)改为(从B借助于A移动到C)
如此可见:我们要明白圆盘具体是怎么移动的
3 代码
#include <bits/stdc++.h>
using namespace std;
void move(char A, char C, int n)
{
cout<<"把第"<<n<<"个圆盘从"<<A<<"--->"<<C<<endl;
}
void HanoiTower(char A, char B, char C, int n)
{
if (n == 1)
{
move(A, C, n);
}
else
{
//将n-1个圆盘从A柱借助于C柱移动到B柱上
HanoiTower(A, C, B, n - 1);
//将A柱子最后一个圆盘移动到C柱上
move(A, C, n);
//将n-1个圆盘从B柱借助于A柱移动到C柱上
HanoiTower(B, A, C, n - 1);
}
}
int main()
{
int n = 0;
cout<<"输入A柱子上的圆盘个数:";
cin>>n;
//将n个圆盘从A柱借助于B柱移动到C柱上
HanoiTower('A', 'B', 'C', n);
return 0;
}
4 加深理解
汉诺四塔
1 思路
1)、将A柱上n个盘子划分为上下两部分,下方部分共有k(1≤k≤n)个盘子,上方部分共有n - k个盘子。
2)、将A柱上面部分n–k个盘子经过C、D柱移至B柱。
3)、将A柱剩余的k个盘子经过C柱移至D柱。
4)、将B柱上的n–k个盘子经过A、C柱移至D柱。
也就是说第二步第四步我们使用汉诺四塔算法,而第三步我们使用汉诺三塔即可。
2 代码
#include <bits/stdc++.h>
using namespace std;
int HanoiTower3(int n, char a, char b, char c) //a->c b做中间柱子
{
if (n == 1)
{
return 1;
}
int now = 0;
now += HanoiTower3(n - 1, a, c, b);
cout<<a<<"->"<<c<<endl;
now++;
now += HanoiTower3(n - 1, b, a, c);
return now;
}
int HanoiTower4(int n, char a, char b, char c, char d)
{
if (n == 1)
{
cout<<a<<"->"<<d<<endl;
return 1;
}
int minans = 100000;
for (int k = 1; k < n; k++)
{
int ans = 0;
ans += HanoiTower4(n - k, a, c, d, b); //n-k指的是上面那部分,k指的是下面部分
ans += HanoiTower3(k, a, c, d);
ans += HanoiTower4(n - k, b, a, c, d);
minans = ans;
}
return minans;
}
int main()
{
int n;
cin >> n;
cout << HanoiTower4(n, 'A', 'B', 'C', 'D') << endl;
return 0;
}