汉诺塔循环解法启蒙---C语言实现

Hanoi汉诺塔循环解法启蒙—C语言实现

首先我们需要清楚一点:这个游戏解法步骤并不固定,但是它的最优解的步骤确是固定的。
提示:本文的思路和焦点在塔尖上,因为无论哪一步塔尖始终在最上方。
试想:我们把塔尖从一个柱子上移动到另一柱子上,接下来的一步一定不会再是移动塔尖,无论是(1.移动回去)还是(2.移动到第三个柱子上),因为这和(1.塔尖不动)或(2.直接移动到第三根上)得到的结果是一样的,并且还多了一步。
我们可以得到一个结论:塔尖不会连续被移动,所以得到塔尖的那个柱子就暂时失去的行动的能力。
接下来的一步一定发生在没有塔尖的两根柱子上。在这两根柱子上的操作只有两种情况把较小的移动过去,或移动到空柱子上。接下来再继续在这两根柱子上移动已经没有任何意义,所以我们有得到一个结论:两根柱子之间不会重复移动,因此下一步我们必须去移动塔尖,移动完塔尖又必须移动其余两个,然后一直重复操作,直到游戏结束。
我们的疑惑是:塔尖到底该怎么移动呢?? 无论是游戏的开始第一步还是以后每次移动塔尖的时候,都会有疑惑,因为有两个选择,下面我们用一个简单的例子解惑:
<我们移动时遵循上面的结论:1.塔尖不会连续被移动 2.两根柱子之间不会重复移动>
我们的目的是要在遵循上面得到的最优解的结论的前提下,去找到塔尖移动的规律。

假设 n=2 即有两个盘子:

ONE:塔尖移动: A->B->C (循环右移)

stepsABC
01/2/0/0/0
1移动塔尖2/01/0/0
2条件移动/01/02/0
3移动塔尖/0/01/2/0

TWO:塔尖移动: A->B->A->B

stepsABC
01/2/0/0/0
1移动塔尖2/01/0/0
2条件移动/01/02/0
3移动塔尖1/0/02/0
4条件移动1/02/00/
5移动塔尖/01/2/00/

当然我们也可以这样:
THREE: 塔尖移动: A->C->B (循环左移)

stepsABC
01/2/0/0/0
1移动塔尖2/0/01/0
2条件移动/02/01/0
3移动塔尖/01/2/0/0

FOUR: 塔尖移动: A->C->A->C

stepsABC
01/2/0/0/0
1移动塔尖2/0/01/0
2条件移动/02/01/0
3移动塔尖1/02/0/0
4条件移动1/0/02/0
5移动塔尖/0/01/2/0

我们甚至可以这样:
FIVE: 塔尖移动: A->B->A->C->A

stepsABC
01/2/0/0/0
1移动塔尖2/01/0/0
2条件移动/01/02/0
3移动塔尖1/0/02/0
4条件移动1/02/0/0
5移动塔尖/02/01/0
6条件移动2/0/01/0
7移动塔尖1/2/0/0/0

可以看到如果任意移动塔尖有可能回到原点

假设 n=3 即有三个盘子:

ONE: 塔尖移动:A->B->C->A->B (称为循环右移)

stepsABC
初始1/2/3/0/0/0
1移动塔尖2/3/01/0/0
2条件移动3/01/02/0
3移动塔尖3/0/01/2/0
4条件移动/03/01/2/0
5移动塔尖1/03/02/0
6条件移动1/02/3/0/0
7移动塔尖/01/2/3/0/0

TWO: 塔尖移动:A->C->B->A->C (称为循环左移)

stepsABC
初始1/2/3/0/0/0
1移动塔尖2/3/0/01/0
2条件移动3/02/01/0
3移动塔尖3/01/2/0/0
4条件移动/01/2/03/0
5移动塔尖1/02/03/0
6条件移动1/0/02/3/0
7移动塔尖/001/2/3/0

THREE: 塔尖移动:A->B->A->C->B->C->A->B

stepsABC
初始1/2/3/0/0/0
1移动塔尖2/3/01/0/0
2条件移动3/01/02/0
3移动塔尖1/3/0/02/0
4条件移动1/3/02/0/0
5移动塔尖3/02/01/0
6条件移动2/3/0/01/0
7移动塔尖2/3/01/0/0
8条件移动3/01/02/0
9移动塔尖3/0/01/2/0
10条件移动/03/01/2/0
11移动塔尖1/03/02/0
12条件移动1/02/3/0/0
13移动塔尖/01/2/3/0/0

由上面的移动我们可以得到两个结论:

  1. 只有塔尖的移动方向是循环左移或循环右移时,我们所需要的步骤才是最小的,并且步骤数相同。即:不论是循环左移还是右移都能得到最优解。
  2. 循环左移和右移最终的结束点是不同的

<数学表述>
n = 2 时:
{ B &lt; = 塔 尖 循 环 左 移 C &lt; = 塔 尖 循 环 右 移 \left\{ \begin{aligned} B &amp; &lt;= &amp;塔尖循环左移 \\ C &amp; &lt;= &amp;塔尖循环右移 \\ \end{aligned} \right. {BC<=<=
n = 3时:
{ B &lt; = 塔 尖 循 环 右 移 C &lt; = 塔 尖 循 环 左 移 \left\{ \begin{aligned} B &amp; &lt;= &amp;塔尖循环右移 \\ C &amp; &lt;= &amp;塔尖循环左移 \\ \end{aligned} \right. {BC<=<=
经过类比和归纳我们可以得到n为偶数时的情况和n = 2时一样, n为奇数时和n = 3时的情况一样, 比如n=1时右移到B左移到C。
故有:
当目标点为B时:
B &lt; = { 塔 尖 循 环 右 移 , n 为 奇 数 塔 尖 循 环 左 移 , n 为 偶 数 B&lt;=\left\{ \begin{aligned} 塔尖循环右移 &amp; , &amp;n为奇数 \\ 塔尖循环左移 &amp; , &amp;n为偶数 \\ \end{aligned} \right. B<={nn
当目标点为C时:
C &lt; = { 塔 尖 循 环 左 移 , n 为 奇 数 塔 尖 循 环 右 移 , n 为 偶 数 C&lt;=\left\{ \begin{aligned} 塔尖循环左移 &amp; , &amp;n为奇数 \\ 塔尖循环右移 &amp; , &amp;n为偶数 \\ \end{aligned} \right. C<={nn
也就是说:如果我们确定了目标柱,并且知道盘子的数目,那么塔尖的移动方向就是固定的了。
到此我们就可以根据上面的结论写程序了, 先就这样,代码以后再贴。

#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));  
    }
}





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值