汉诺塔循环解法启蒙---C语言实现
Hanoi汉诺塔循环解法启蒙—C语言实现
首先我们需要清楚一点:这个游戏解法步骤并不固定,但是它的最优解的步骤确是固定的。
提示:本文的思路和焦点在塔尖上,因为无论哪一步塔尖始终在最上方。
试想:我们把塔尖从一个柱子上移动到另一柱子上,接下来的一步一定不会再是移动塔尖,无论是(1.移动回去)还是(2.移动到第三个柱子上),因为这和(1.塔尖不动)或(2.直接移动到第三根上)得到的结果是一样的,并且还多了一步。
我们可以得到一个结论:塔尖不会连续被移动,所以得到塔尖的那个柱子就暂时失去的行动的能力。
接下来的一步一定发生在没有塔尖的两根柱子上。在这两根柱子上的操作只有两种情况:把较小的移动过去,或移动到空柱子上。接下来再继续在这两根柱子上移动已经没有任何意义,所以我们有得到一个结论:两根柱子之间不会重复移动,因此下一步我们必须去移动塔尖,移动完塔尖又必须移动其余两个,然后一直重复操作,直到游戏结束。
我们的疑惑是:塔尖到底该怎么移动呢?? 无论是游戏的开始第一步还是以后每次移动塔尖的时候,都会有疑惑,因为有两个选择,下面我们用一个简单的例子解惑:
<我们移动时遵循上面的结论:1.塔尖不会连续被移动 2.两根柱子之间不会重复移动>
我们的目的是要在遵循上面得到的最优解的结论的前提下,去找到塔尖移动的规律。
假设 n=2 即有两个盘子:
ONE:塔尖移动: A->B->C (循环右移)
steps | A | B | C |
---|---|---|---|
0 | 1/2/0 | /0 | /0 |
1移动塔尖 | 2/0 | 1/0 | /0 |
2条件移动 | /0 | 1/0 | 2/0 |
3移动塔尖 | /0 | /0 | 1/2/0 |
TWO:塔尖移动: A->B->A->B
steps | A | B | C |
---|---|---|---|
0 | 1/2/0 | /0 | /0 |
1移动塔尖 | 2/0 | 1/0 | /0 |
2条件移动 | /0 | 1/0 | 2/0 |
3移动塔尖 | 1/0 | /0 | 2/0 |
4条件移动 | 1/0 | 2/0 | 0/ |
5移动塔尖 | /0 | 1/2/0 | 0/ |
当然我们也可以这样:
THREE: 塔尖移动: A->C->B (循环左移)
steps | A | B | C |
---|---|---|---|
0 | 1/2/0 | /0 | /0 |
1移动塔尖 | 2/0 | /0 | 1/0 |
2条件移动 | /0 | 2/0 | 1/0 |
3移动塔尖 | /0 | 1/2/0 | /0 |
FOUR: 塔尖移动: A->C->A->C
steps | A | B | C |
---|---|---|---|
0 | 1/2/0 | /0 | /0 |
1移动塔尖 | 2/0 | /0 | 1/0 |
2条件移动 | /0 | 2/0 | 1/0 |
3移动塔尖 | 1/0 | 2/0 | /0 |
4条件移动 | 1/0 | /0 | 2/0 |
5移动塔尖 | /0 | /0 | 1/2/0 |
我们甚至可以这样:
FIVE: 塔尖移动: A->B->A->C->A
steps | A | B | C |
---|---|---|---|
0 | 1/2/0 | /0 | /0 |
1移动塔尖 | 2/0 | 1/0 | /0 |
2条件移动 | /0 | 1/0 | 2/0 |
3移动塔尖 | 1/0 | /0 | 2/0 |
4条件移动 | 1/0 | 2/0 | /0 |
5移动塔尖 | /0 | 2/0 | 1/0 |
6条件移动 | 2/0 | /0 | 1/0 |
7移动塔尖 | 1/2/0 | /0 | /0 |
可以看到如果任意移动塔尖有可能回到原点
假设 n=3 即有三个盘子:
ONE: 塔尖移动:A->B->C->A->B (称为循环右移)
steps | A | B | C |
---|---|---|---|
初始 | 1/2/3/0 | /0 | /0 |
1移动塔尖 | 2/3/0 | 1/0 | /0 |
2条件移动 | 3/0 | 1/0 | 2/0 |
3移动塔尖 | 3/0 | /0 | 1/2/0 |
4条件移动 | /0 | 3/0 | 1/2/0 |
5移动塔尖 | 1/0 | 3/0 | 2/0 |
6条件移动 | 1/0 | 2/3/0 | /0 |
7移动塔尖 | /0 | 1/2/3/0 | /0 |
TWO: 塔尖移动:A->C->B->A->C (称为循环左移)
steps | A | B | C |
---|---|---|---|
初始 | 1/2/3/0 | /0 | /0 |
1移动塔尖 | 2/3/0 | /0 | 1/0 |
2条件移动 | 3/0 | 2/0 | 1/0 |
3移动塔尖 | 3/0 | 1/2/0 | /0 |
4条件移动 | /0 | 1/2/0 | 3/0 |
5移动塔尖 | 1/0 | 2/0 | 3/0 |
6条件移动 | 1/0 | /0 | 2/3/0 |
7移动塔尖 | /0 | 0 | 1/2/3/0 |
THREE: 塔尖移动:A->B->A->C->B->C->A->B
steps | A | B | C |
---|---|---|---|
初始 | 1/2/3/0 | /0 | /0 |
1移动塔尖 | 2/3/0 | 1/0 | /0 |
2条件移动 | 3/0 | 1/0 | 2/0 |
3移动塔尖 | 1/3/0 | /0 | 2/0 |
4条件移动 | 1/3/0 | 2/0 | /0 |
5移动塔尖 | 3/0 | 2/0 | 1/0 |
6条件移动 | 2/3/0 | /0 | 1/0 |
7移动塔尖 | 2/3/0 | 1/0 | /0 |
8条件移动 | 3/0 | 1/0 | 2/0 |
9移动塔尖 | 3/0 | /0 | 1/2/0 |
10条件移动 | /0 | 3/0 | 1/2/0 |
11移动塔尖 | 1/0 | 3/0 | 2/0 |
12条件移动 | 1/0 | 2/3/0 | /0 |
13移动塔尖 | /0 | 1/2/3/0 | /0 |
由上面的移动我们可以得到两个结论:
- 只有塔尖的移动方向是循环左移或循环右移时,我们所需要的步骤才是最小的,并且步骤数相同。即:不论是循环左移还是右移都能得到最优解。
- 循环左移和右移最终的结束点是不同的
<数学表述>
n = 2 时:
{B<=塔尖循环左移C<=塔尖循环右移 \left\{
\begin{aligned}
B & <= &塔尖循环左移 \\
C & <= &塔尖循环右移 \\
\end{aligned}
\right.
{BC<=<=塔尖循环左移塔尖循环右移
n = 3时:
{B<=塔尖循环右移C<=塔尖循环左移 \left\{
\begin{aligned}
B & <= &塔尖循环右移 \\
C & <= &塔尖循环左移 \\
\end{aligned}
\right.
{BC<=<=塔尖循环右移塔尖循环左移
经过类比和归纳我们可以得到n为偶数时的情况和n = 2时一样, n为奇数时和n = 3时的情况一样, 比如n=1时右移到B左移到C。
故有:
当目标点为B时:
B<={塔尖循环右移,n为奇数塔尖循环左移,n为偶数 B<=\left\{
\begin{aligned}
塔尖循环右移 & , &n为奇数 \\
塔尖循环左移 & , &n为偶数 \\
\end{aligned}
\right.
B<={塔尖循环右移塔尖循环左移,,n为奇数n为偶数
当目标点为C时:
C<={塔尖循环左移,n为奇数塔尖循环右移,n为偶数 C<=\left\{
\begin{aligned}
塔尖循环左移 & , &n为奇数 \\
塔尖循环右移 & , &n为偶数 \\
\end{aligned}
\right.
C<={塔尖循环左移塔尖循环右移,,n为奇数n为偶数
也就是说:如果我们确定了目标柱,并且知道盘子的数目,那么塔尖的移动方向就是固定的了。
到此我们就可以根据上面的结论写程序了, 先就这样,代码以后再贴。
#include <stdio.h> #include <stdlib.h> #include <memory.h> #define TIP 1 #define NUMEL(x) (x->esp - x->ebp) //calculate the number of element in stack x #define POP(x) (x->esp -= 1) //POP stack #define PUSH(x, y) {x->esp += 1; *x->esp = y;} //PUSH y in stack #define ISEMPTY(x) (*x->esp == 0) //judge whether the stack is empty #define VALUE(x) (*x->esp) //get the top VALUE of the stack #define LABEL(x) (x->label) //get the label of the stack int main() { int n; void Hanoi_Iteration(int n); printf("Enter the value of disks: "); scanf("%d", &n); if(n <= 0) { printf("Enter a number greater than 0\n\n"); return 0; } Hanoi_Iteration(n); return 0; } typedef struct { int *esp; //always point to the top of the stack int *ebp; //always point to the bottom of the stack char label; //store the Tower LABEL such as A, B, C }ST,*PST; // initialize structure PST createTower(int n) { PST ptower = (PST)malloc(sizeof(ST)); if(ptower == NULL) { printf("Fail to allocate memory\n"); exit(0); } ptower->esp = ptower->ebp = (int*)malloc(sizeof(int)*(n+1)); if(ptower->esp == NULL) { printf("Fail to allocate memory\n"); exit(0); } *ptower->ebp = 0; //we use the number zero to represent a empty tower return ptower; } void freeTower(PST ptower) { free(ptower->ebp); ptower->esp = ptower->ebp = NULL; free(ptower); } void Hanoi_Iteration(int n) { int i = n; PST current = createTower(n), next = createTower(n), other = createTower(n), temp; void steps(PST current, PST next, PST other, int n); if(n % 2 == 0) //if even, rotate right { LABEL(current) = 'A'; LABEL(next) = 'B', LABEL(other) = 'C'; } else //if odd, rotate left { LABEL(current) = 'A'; LABEL(next) = 'C', LABEL(other) = 'B'; } //initialize the original Tower/pole for(;i>0;i--) { PUSH(current, i) } printf("The sequence of moves involved in the Tower of Hanoi are:\n\n"); do{ steps(current, next, other, n); //move temp = current; current = next; next = other; other = temp; //set the pole which has the tip(next) as current, other as next, current as other. }while(!(NUMEL(current) == n || NUMEL(other) == n || NUMEL(next) == n)); //when any tower regains all the disks, the game's over freeTower(current); freeTower(next); freeTower(other); current = next = other = NULL; } void steps(PST current, PST next, PST other, int n) { //1. move TIP to the next PUSH(next, TIP); POP(current); printf("Move disk 1 from peg %c to peg %c\n", LABEL(current), LABEL(next)); if(NUMEL(current) == n || NUMEL(other) == n || NUMEL(next) == n) return; //2. campare current and other and then move the smaller disk to their opposite or move directly to the empty pole. if(!ISEMPTY(current) && VALUE(current) < VALUE(other) || ISEMPTY(other)) { PUSH(other, VALUE(current)); POP(current); printf("Move disk %d from peg %c to peg %c\n", VALUE(other), LABEL(current), LABEL(other)); } else { PUSH(current, VALUE(other)); POP(other); printf("Move disk %d from peg %c to peg %c\n",VALUE(current), LABEL(other), LABEL(current)); } }
- 汉诺塔解法C语言实现
- C语言实现循环队列的初始化&进队&出队&读取队头元素&判空-2
- c语言:单向循环链表的实现
- 数据结构:循环队列(C语言实现)
- C语言循环的实现
- C语言信号量实现两线程循环打印
- 数据结构:循环队列(C语言实现)
- 数据结构:循环队列(C语言实现)
- c语言实现双向循环链表以及部分函数功能
- C语言使用非循环双向链表实现队列
- C语言实现双向循环链表
- 循环队列以及链队列的实现(C语言)
- C语言实现双向循环链表
- 汉诺塔,python递归实现解法步骤
- 数据结构双向循环链表的C语言实现(插入,查询,删除)
- 循环队列实现(C语言)
- 双向循环链表基本操作的实现(C语言)
- c语言之————有头循环双链表实现队列存储
- c语言实现循环链表的基本操作
- 双向循环链表linux中C语言实现双向循环链表