汉诺塔问题——“分步和”的规律(一种全新的理解)

汉诺塔是一个经典问题,相传在古印度圣庙中,有一种被称为汉诺塔(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个圆盘的汉诺塔问题解如下图:
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  |
//                                                                                                           |
//——————————————————————————————————————————————————————

猜你喜欢

转载自blog.csdn.net/SongXJ_01/article/details/104226426