漢諾塔問題

問題一

現在有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;
    }
};




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章