汉诺塔问题

问题一

现在有n个圆盘从上往下从小到大叠在第一根柱子上,要把这些圆盘全部移动到第三根柱子要怎么移动呢?请找出需要步骤数最少的方案

因此我们可以将问题简化描述为:n个盘子和3根柱子:A(源)、B(备用)、C(目的),盘子的大小不同且中间有一孔,可以将盘子“串”在柱子上,每个盘子只能放在比它大的盘子上面。起初,所有盘子在A柱上,问题是将盘子一个一个地从A柱子移动到C柱子。移动过程中,可以使用B柱,但盘子也只能放在比它大的盘子上面。

因此我们得出汉诺塔问题的以下几个限制条件:

1.在小圆盘上不能放大圆盘。

2.在三根柱子之间一回只能移动一个圆盘。

3.只能移动在最顶端的圆盘。

首先,我们从简单的例子开始分析,然后再总结出一般规律。

当n = 1的时候,即此时只有一个盘子,那么直接将其移动至C即可。移动过程就是 A -> C

当n = 2的时候,这时候有两个盘子,那么在一开始移动的时候,我们需要借助B柱作为过渡的柱子,即将A柱最上面的那个小圆盘移至B柱,然后将A柱底下的圆盘移至C柱,最后将B柱的圆盘移至C柱即可。那么完整移动过程就是A -> B , A -> C , B -> C

当n = 3的时候,那么此时从上到下依次摆放着从小到大的三个圆盘,根据题目的限制条件:在小圆盘上不能放大圆盘,而且把圆盘从A柱移至C柱后,C柱圆盘的摆放情况和刚开始A柱的是一模一样的。所以呢,我们每次移至C柱的圆盘(移至C柱后不再移到其他柱子上去),必须是从大到小的,即一开始的时候,我们应该想办法把最大的圆盘移至C柱,然后再想办法将第二大的圆盘移至C柱……然后重复这样的过程,直到所有的圆盘都按照原来A柱摆放的样子移动到了C柱。

那么根据这样的思路,问题就来了:

如何才能够将最大的盘子移至C柱呢?

那么我们从问题入手,要将最大的盘子移至C柱,那么必然要先搬掉A柱上面的n-1个盘子,而C柱一开始的时候是作为目标柱的,所以我们可以用B柱作为”暂存”这n-1个盘子的过渡柱,当把这n-1的盘子移至B柱后,我们就可以把A柱最底下的盘子移至C柱了。

而接下来的问题是什么呢?

我们来看看现在各个柱子上盘子的情况,A柱上无盘子,而B柱从上到下依次摆放着从小到大的n-1个盘子,C柱上摆放着最大的那个盘子。

所以接下来的问题就显而易见了,那就是要把B柱这剩下的n-1个盘子移至C柱,而B柱作为过渡柱,那么我们需要借助A柱,将A柱作为新的”过渡”柱,将这n-1个盘子移至C柱。

根据上面的分析,我们可以抽象得出这样的结论:

汉诺塔函数原型:

void Hanio(int n,char start_pos,char tran_pos,char end_pos)

那么我们把n个盘子从A柱移动至C柱的问题可以表示为:

Hanio(n,A,B,C);

那么从上面的分析得出:

该问题可以分解成以下子问题:

第一步:将n-1个盘子从A柱移动至B柱(借助C柱为过渡柱)

第二步:将A柱底下最大的盘子移动至C柱

第三步:将B柱的n-1个盘子移至C柱(借助A柱为过渡柱)

因此完整代码如下所示:

#include<cstdio>  
02.int i;    //记录步数  
03.//i表示进行到的步数,将编号为n的盘子由from柱移动到to柱(目标柱)  
04.void move(int n,char from,char to){  
05.    printf("第%d步:将%d号盘子%c---->%c\n",i++,n,from,to);  
06.}  
07.  
08.//汉诺塔递归函数  
09.//n表示要将多少个"圆盘"从起始柱子移动至目标柱子  
10.//start_pos表示起始柱子,tran_pos表示过渡柱子,end_pos表示目标柱子  
11.void Hanio(int n,char start_pos,char tran_pos,char end_pos){  
12.    if(n==1){    //很明显,当n==1的时候,我们只需要直接将圆盘从起始柱子移至目标柱子即可.  
13.        move(n,start_pos,end_pos);  
14.    }  
15.    else{  
16.        Hanio(n-1,start_pos,end_pos,tran_pos);   //递归处理,一开始的时候,先将n-1个盘子移至过渡柱上  
17.        move(n,start_pos,end_pos);                //然后再将底下的大盘子直接移至目标柱子即可  
18.        Hanio(n-1,tran_pos,start_pos,end_pos);    //然后重复以上步骤,递归处理放在过渡柱上的n-1个盘子  
19.                                                  //此时借助原来的起始柱作为过渡柱(因为起始柱已经空了)  
20.    }  
21.}  
22.int main(){  
23.    int n;  
24.    while(scanf("%d",&n)==1&&n){  
25.        i = 1;   //全局变量赋初始值  
26.        Hanio(n,'1','2','3');  
27.        printf("最后总的步数为%d\n",i-1);  
28.    }  
29.    return 0;  
30.}  


问题二
有一个int数组arr其中只含有1、2和3,分别代表所有圆盘目前的状态,1代表左柱,2代表中柱,3代表右柱,arr[i]的值代表第i+1个圆盘的位置。比如,arr=[3,3,2,1],代表第1个圆盘在右柱上、第2个圆盘在右柱上、第3个圆盘在中柱上、第4个圆盘在左柱上。如果arr代表的状态是最优移动轨迹过程中出现的状态,返回arr这种状态是最优移动轨迹中的第几个状态。如果arr代表的状态不是最优移动轨迹过程中出现的状态,则返回-1。
给定一个int数组arr及数组的大小n,含义如题所述,请返回一个int,代表所求的结果。
测试样例:
[3,3]
返回:3 递归版本

class Hanoi {
public:
    int process(vector<int> &arr,int i,int from,int mid,int to){
        if(i==-1)
            return 0;
        if(arr[i]!=from && arr[i]!=to)
            return -1;
        if(arr[i]==from)
            return process(arr,i-1,from,to,mid);
        else{
            int res=process(arr,i-1,mid,from,to);
            if(res==-1)
                return -1;
            return (1<<i)+res;
        }
    }
    int chkStep(vector<int> arr, int n) {
        // write code here
        if(arr.empty()||n<=0)
            return -1;
        return process(arr,n-1,1,2,3);
    }
};

非递归版本

class Hanoi {
public:
    int chkStep(vector<int> arr, int n) {
        // write code here
        if(arr.empty()||n<=0)
            return -1;
        int from=1,mid=2,to=3;
        int rest=0,tmp=0;
        while(n>=1){
            if(arr[n-1]!=from && arr[n-1]!=to)
                return -1;
            if(arr[n-1]==to){
                rest+=1<<(n-1);
                tmp=from;
                from=mid;
            }
            else{
                tmp=to;
                to=mid;
            }
            mid=tmp;
            n--;
        }
        return rest;
    }
};
点赞