汉诺塔是一个经典问题,相传在古印度圣庙中,有一种被称为汉诺塔(Hanoi)的游戏。该游戏是在一块铜板装置上,有三根杆(编号A、B、C),在A杆自下而上、由大到小按顺序放置n个金盘。游戏的目标:把A杆上的金盘全部移到C杆上,并仍保持原有顺序叠好。操作规则:每次只能移动一个盘子,并且在移动过程中三根杆上都始终保持大盘在下,小盘在上,操作过程中盘子可以置于A、B、C任一杆上。
这是一个经典的递归算法,相信大家在学递归的时候,老师一定会给大家讲这个例子的。
具体的过程代码如下:
#include <iostream>
#include <cstdio>
using namespace std;
int cnt;
void move(int id, char from, char to) {// 打印移动方式:编号,从哪个盘子移动到哪个盘子
printf("step %d: move %d from %c->%c\n", ++cnt, id, from, to);
}
void hanoi(int n, char x, char y, char z){
if (n == 0)
return;
hanoi(n - 1, x, z, y);
move(n, x, z);
hanoi(n - 1, y, x, z);
}
int main()
{
int n;
cnt = 0;
cin>>n;
hanoi(n, 'A', 'B', 'C');
return 0;
}
但这个不是我们今天的重点,我们今天的重点是,如何快速解决 A->B,A->C,B->A,B->C,C->A,C->B的次数,以及所有移动的总步数。
第一种方法就是通过上面的代码进行递归并计数,是一定可以找到结果的,但是我相信美妙的数学世界一定是有规律可循的。
1~10个圆盘的汉诺塔问题解如下图:
从中我们可以找到如下相等关系:
n个圆盘的汉诺塔问题
(为方便表示,A->B的次数用 ab 表示,A->C的次数用 ac 表示,其他同理)
1.ab = bc
2.ba = cb
3.ab 与 cb 的关系:
n为奇数时:ab x 2 - ( ( n - 1 ) / 2 ) = cb
n为偶数时:cb x 2 + ( n / 2 ) = ab
4. ca 与 cb 的关系:
n为奇数时:ca x 2 + ( ( n - 1 ) / 2 ) = cb
n为偶数时:ca = 2 x cb
5.ac 与 ca 的关系:
n为奇数时:ca x 2 + n = ac
n为偶数时:ac x 2 - n = ca
一个最重要的递推关系:
n为奇数时:aci = aci+1
n为偶数时:cai = cai+1
通过这个递推关系可以将上面的 5 个关系串起来,确定 n 后通过奇偶确定 ac 或 ca 然后通过 1~5 确定剩下 5 个变量。
至于总操作数,我们可以通过对 ab,ac,ba,bc,ca,cb 求和,当然也可以单独找规律算,这个规律网上有很多解释的文章,具体公式为:sum = 2n - 1
总步骤数的详解
具体代码如下:
#include <bits/stdc++.h>
using namespace std;
int main(){
int n;
long long ab = 0, ac = 1, ba = 0, bc = 0, ca = 0, cb = 0;
cin >> n;
for (int i = 1; i <= n; ++i) { //从1开始迭代到n,计算ac和ca的值
if (i % 2 == 0)
ca = ac * 2 - i;
else {
ac = ca * 2 + i;
}
}
if (n % 2 == 0) { //如果是偶数,以ca为准计算
cb = ca / 2;
ba = cb;
ab = cb * 2 + n / 2;
bc = ab;
ac = (ca + n) / 2;
}
else { //如果是奇数,以ac为准计算
ca = (ac - n) / 2;
cb = ca * 2 + ((n - 1) / 2);
ab = (cb + ((n - 1) / 2)) / 2;
bc = ab;
ba = cb;
}
//输出
cout << "A->B:" << ab << endl;
cout << "A->C:" << ac << endl;
cout << "B->A:" << ba << endl;
cout << "B->C:" << bc << endl;
cout << "C->A:" << ca << endl;
cout << "C->B:" << cb << endl;
cout << "SUM:" << ab + ac + ba + bc + ca + cb;
return 0;
}
下面是网友使用同种思想写的更加简洁的递推代码,再次作为收录以供大家学习与个人进步:
(来源:601打铁匠的博客)
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll dp[65][10], n, res;
string s[] = { "A->B:","A->C:","B->A:","B->C:","C->A:","C->B:" };
signed main() {
cin >> n;
dp[1][2] = 1;
for (int i = 2; i <= n; i++) {
dp[i][1] = dp[i][4] = dp[i - 1][2] + dp[i - 1][3];
dp[i][3] = dp[i][6] = dp[i - 1][4] + dp[i - 1][5];
dp[i][2] = dp[i - 1][1] * 2 + 1;
dp[i][5] = dp[i - 1][6] * 2;
}
for (int i = 0; i < 6; i++) cout << s[i] << dp[n][i + 1] << endl, res += dp[n][i + 1];
cout << "SUM:" << res << endl;
return 0;
}
//前20组数组
//——————————————————————————————————————————————————————
//数目:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
// |
//A->B: 0 1 1 4 4 15 15 58 58 229 229 912 912 3643 3643 14566 14566 58257 58257 233020 |
//A->C: 1 1 3 3 9 9 31 31 117 117 459 459 1825 1825 7287 7287 29133 29133 116515 116515 |
//B->A: 0 0 1 1 6 6 27 27 112 112 453 453 1818 1818 7279 7279 29124 29124 116505 116505 |
//B->C: 0 1 1 4 4 15 15 58 58 229 229 912 912 3643 3643 14566 14566 58257 58257 233020 |
//C->A: 0 0 0 2 2 12 12 54 54 224 224 906 906 3636 3636 14558 14558 58248 58248 233010 |
//C->B: 0 0 1 1 6 6 27 27 112 112 453 453 1818 1818 7279 7279 29124 29124 116505 116505 |
// |
//——————————————————————————————————————————————————————