1. 问题描述
有 3 根柱子 A、B、C,A 柱子从下至上放置着从大到小的
n
n
n 个盘子。现要求利用 B 把 A 上的盘子全都挪到 C 上,挪动条件为:
(1)一次只能移动一个盘子;
(2)移动过程中不能出现小的盘子在大的盘子下面。
以
n
=
3
n=3
n=3 为例,因为盘子数量少,很容易就能得到下面的这种移动方式。
1
2
3
A
B
C
\begin{matrix} 1& & \\ 2& & \\ 3& & \\ \mathrm{A}&\mathrm{B}&\mathrm{C} \end{matrix}
123ABC
→
\to
→
2
3
1
A
B
C
\begin{matrix} & & \\ 2& & \\ 3& &1 \\ \mathrm{A}&\mathrm{B}&\mathrm{C} \end{matrix}
23AB1C
→
\to
→
3
2
1
A
B
C
\begin{matrix} & & \\ & & \\ 3& 2&1 \\ \mathrm{A}&\mathrm{B}&\mathrm{C} \end{matrix}
3A2B1C
→
\to
→
1
3
2
A
B
C
\begin{matrix} & & \\ & 1& \\ 3& 2& \\ \mathrm{A}&\mathrm{B}&\mathrm{C} \end{matrix}
3A12BC
→
\to
→
1
2
3
A
B
C
\begin{matrix} & & \\ & 1& \\ & 2&3 \\ \mathrm{A}&\mathrm{B}&\mathrm{C} \end{matrix}
A12B3C
→
\to
→
1
2
3
A
B
C
\begin{matrix} & & \\ & & \\ 1& 2&3 \\ \mathrm{A}&\mathrm{B}&\mathrm{C} \end{matrix}
1A2B3C
→
\to
→
2
1
3
A
B
C
\begin{matrix} & & \\ & &2 \\ 1& &3 \\ \mathrm{A}&\mathrm{B}&\mathrm{C} \end{matrix}
1AB23C
→
\to
→
1
2
3
A
B
C
\begin{matrix} & &1 \\ & &2 \\ & &3 \\ \mathrm{A}&\mathrm{B}&\mathrm{C} \end{matrix}
AB123C
2. 解题思路
看
n
=
3
n=3
n=3 的移动方式,其根本的思路是:
(1)把 A 上的 1 和 2 移动到 B 上;
(2)把 A 剩下最大的 3 移动到 C 上;
(3)把 B 上的 1 和 2 移动到 C 上;
拓展到
n
n
n 个盘子:
(1)把 A 上的
1
∼
n
−
1
1\sim n-1
1∼n−1 移动到 B 上;
(2)把 A 剩下最大的
n
n
n 移动到 C 上;
(3)把 B 上的
1
∼
n
−
1
1\sim n-1
1∼n−1 移动到 C 上;
可以发现(1、3)步骤只是一个
n
−
1
n-1
n−1 的汉诺塔问题,只不过把 A 移到 C 变成了 A 移到 B 和 B 移到 C。由此得到递归求解的思路。
3. 移动步骤代码实现
#include <stdio.h>
void Hanoi(int n, char A, char B, char C){
if (n == 1)
printf("%c --> %c\n", A, C);
else{
Hanoi(n - 1, A, C, B);
printf("%c --> %c\n", A, C);
Hanoi(n - 1, B, A, C);
}
}
main() {
Hanoi(3, 'A', 'B', 'C');
return 0;
}
输出结果:
A --> C
A --> B
C --> B
A --> C
B --> A
B --> C
A --> C
4. 移动步骤数量计算
根据上面的移动规律可知,要完成一次
n
n
n 的汉诺塔,里面会包含两个
n
−
1
n-1
n−1 的汉诺塔加上 1 次移动,
S
n
=
2
∗
S
n
−
1
+
1
S_n = 2*S_{n-1}+1
Sn=2∗Sn−1+1
S
1
=
1
S_1 = 1
S1=1
S
2
=
2
∗
S
1
+
1
=
2
∗
1
+
1
=
3
S_2 = 2*S_1+1 = 2*1+1=3
S2=2∗S1+1=2∗1+1=3
S
3
=
2
∗
S
2
+
1
=
2
∗
(
2
∗
1
+
1
)
+
1
=
7
S_3 = 2*S_2+1=2*(2*1+1)+1=7
S3=2∗S2+1=2∗(2∗1+1)+1=7
可以发现其中的规律是
S
n
=
2
n
−
1
+
2
n
−
2
+
⋯
+
2
0
S_n = 2^{n-1}+2^{n-2}+\cdots+2^0
Sn=2n−1+2n−2+⋯+20,可以按等比数列求和或是看作
n
n
n 位全为 1 的二进制数对应的十进制值,也就是
S
n
=
2
n
−
1
S_n = 2^n-1
Sn=2n−1。
5. 实现真正的移动
上面的代码只是给出了如何移动的步骤,但是并没有真正在三个塔上实现移动。通常的汉诺塔任务给出步骤即可,这里加一点点难度,构建出三个塔把每次移动后的状态都打印出来,实现真正的移动。
根据移动方式,只会把某个塔最上面的盘子放到另一个塔的最上面,想到的是把每个塔当做一个链表,每个盘子是一个节点,塔的底部为链表的头,塔的顶部为链表的尾。移动的时候会把一个塔的尾部节点移到另一个塔的尾部,由此一来最好记录一下尾部节点的地址,头部地址就用来从下往上打印塔上的盘子。
链表构建代码如下:
typedef int TDataType;
typedef struct TowerNode {
TDataType data;
struct TowerNode* next;
struct TowerNode* prev;
}TowerNode;
typedef struct Tower {
TowerNode* head;
TowerNode* tail;
}Tower;
void TowerInit(Tower* pt) {
assert(pt);
pt->head = NULL;
pt->tail = NULL;
}
void TowerPush(Tower* pt, TDataType data) {
assert(pt);
TowerNode* newnode = (TowerNode*)malloc(sizeof(TowerNode));
newnode->data = data;
newnode->next = NULL;
if (pt->head == NULL) {
newnode->prev = NULL;
pt->head = pt->tail = newnode;
}
else {
newnode->prev = pt->tail;
pt->tail->next = newnode;
pt->tail = newnode;
}
}
void TowerDestroy(Tower* pt) {
assert(pt);
TowerNode* cur = pt->head;
while (cur) {
TowerNode* next = cur->next;
free(cur);
cur = next;
}
pt->head = pt->tail = NULL;
}
void TowerMove(Tower* ptsrc, Tower* pttgt) {
assert(ptsrc);
assert(pttgt);
TowerNode* srcnode = ptsrc->tail;
if (srcnode->prev) {
srcnode->prev->next = NULL;
ptsrc->tail = srcnode->prev;
}
else {
ptsrc->head = ptsrc->tail = NULL;
}
if (pttgt->head) {
pttgt->tail->next = srcnode;
srcnode->prev = pttgt->tail;
pttgt->tail = srcnode;
}
else {
pttgt->head = pttgt->tail = srcnode;
srcnode->prev = NULL;
}
}
void PrintTower(Tower t) {
TowerNode* cur = t.head;
while (cur) {
printf("%d ", cur->data);
cur = cur->next;
}
printf("\n");
}
移动和打印代码如下:
代码的逻辑是一样,只是为了确保打印塔的时候每次都是按 ABC 的顺序,把三个链表放在一个数组中。
void PrintTowerList(Tower* towerlist[3]) {
printf("A: ");
PrintTower(*towerlist[0]);
printf("B: ");
PrintTower(*towerlist[1]);
printf("C: ");
PrintTower(*towerlist[2]);
}
void Hanoi(int n, Tower* towerlist[3], int a, int b, int c) {
static int count = 1;
char str[] = { 'A', 'B', 'C' };
if (n == 1) {
TowerMove(towerlist[a], towerlist[c]);
printf("第%d步: %c --> %c\n", count, str[a], str[c]);
count++;
PrintTowerList(towerlist);
}
else {
Hanoi(n - 1, towerlist, a, c, b);
TowerMove(towerlist[a], towerlist[c]);
printf("第%d步: %c --> %c\n", count, str[a], str[c]);
count++;
PrintTowerList(towerlist);
Hanoi(n - 1, towerlist, b, a, c);
}
}
int main()
{
int n = 3;
Tower towerA, towerB, towerC;
TowerInit(&towerA);
TowerInit(&towerB);
TowerInit(&towerC);
for (int i = n; i > 0; i--) {
TowerPush(&towerA, i);
}
Tower* towerlist[3] = { &towerA, &towerB, &towerC };
printf("初始状态\n");
PrintTowerList(towerlist);
Hanoi(n, towerlist, 0, 1, 2);
TowerDestroy(&towerA);
TowerDestroy(&towerB);
TowerDestroy(&towerC);
return 0;
}
输出结果:
初始状态
A: 3 2 1
B:
C:
第1步: A --> C
A: 3 2
B:
C: 1
第2步: A --> B
A: 3
B: 2
C: 1
第3步: C --> B
A: 3
B: 2 1
C:
第4步: A --> C
A:
B: 2 1
C: 3
第5步: B --> A
A: 1
B: 2
C: 3
第6步: B --> C
A: 1
B:
C: 3 2
第7步: A --> C
A:
B:
C: 3 2 1