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
文章评论