C语言——汉诺塔问题(移动步骤、步骤数量、实现真正移动)

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 1n1 移动到 B 上;
(2)把 A 剩下最大的 n n n 移动到 C 上;
(3)把 B 上的 1 ∼ n − 1 1\sim n-1 1n1 移动到 C 上;
  可以发现(1、3)步骤只是一个 n − 1 n-1 n1 的汉诺塔问题,只不过把 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 n1 的汉诺塔加上 1 次移动, S n = 2 ∗ S n − 1 + 1 S_n = 2*S_{n-1}+1 Sn=2Sn1+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=2S1+1=21+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=2S2+1=2(21+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=2n1+2n2++20,可以按等比数列求和或是看作 n n n 位全为 1 的二进制数对应的十进制值,也就是 S n = 2 n − 1 S_n = 2^n-1 Sn=2n1

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: 12: A --> B
A: 3
B: 2
C: 13: C --> B
A: 3
B: 2 1
C:4: A --> C
A:
B: 2 1
C: 35: B --> A
A: 1
B: 2
C: 36: B --> C
A: 1
B:
C: 3 27: A --> C
A:
B:
C: 3 2 1

猜你喜欢

转载自blog.csdn.net/weixin_43605641/article/details/124799050