色久悠悠A∨在线|七只鸭子电影网|伦理电影在线看|欧产日产国产精品精品|热の中文 AV天堂|久久香蕉国产精品视频|japonensis成熟

中華文教網移動版

首頁>>課題研究>>

漢諾塔問題的算法分析及C++實現

漢諾塔問題的算法分析及C++實現

江蘇灌南縣  孫小慧

   摘要:該文對經典的漢諾塔問題進行了詳細的分析,給出了遞歸的和非遞歸的算法,并用c++語言對這兩種算法進行了實現與比較。

    關鍵字 漢諾塔,遞歸算法,非遞歸算法

Algorithm Analysis and C++ Realization of Hanio Issue

Sun Xiaohui

      Abstract: This text carries on detailed analysis about classical Hanio issue ,then describe it with both Recursive algorithm and Non-recursive algorithm, provides realization of algorithm in C++.

KeywordsTower of hanoiRecursive algorithmNon-recursive algorithm

      一、 問題描述

問題提出:

設有三個塔柱(分別為A號,B號和C號)。初始時,有個圓盤按自下向上、從大到小的次序疊置在塔上。現要將塔上的所有圓盤,借助于B塔,全部移動到C塔上,且仍按照原來的次序疊置。移動的規則如下:這些圓形盤只能在個塔間進行移動,一次只能移動一個盤子,且任何時候都不允許將較大的盤子壓在比它小的盤子的上面。要求如下:從鍵盤輸入初始圓形盤子個數n,用語言實現個盤子最佳移動的全過程[1]

本題的目的是設計一個盤子移動的方案,使得號塔上的所有盤子借助于B塔按照原來的次序移動到C塔上,并且,要給出完整的盤子移動的方案。下面我們從遞歸和非遞歸兩種方面進行考慮[2]

       二、 遞歸解法及其C++實現

我們先來考慮下遞歸求解這個問題的情況:

假設共有n個盤子,無論n是多少,也不管怎么去移動盤子(當然是按規則來移動),但在移動的過程中,將始終會出現這樣的狀態情況:(n1)個盤子將會以自下向上、從大到小的次序疊置在塔上,這時,A塔上第個盤子就能被輕而易舉地疊放到塔上;接著,再把塔上的共(n1)個盤子移動到塔上,問題好像已經解決。但,塔上(n1)個盤子怎么移動到塔上呢芽這是要解決的第二個問題。同樣,不管我們怎么移動,也將會出現這樣的狀態情況:(n2 個盤子將會以從上到下、從大到小的次序疊置在塔上,這時,塔上第(n1 個盤子就能被輕而易舉放到塔上;接著,把塔上的共(n2)個盤子移動到塔上。這樣,不斷深入,不斷細小化,最終,將到達僅有一個盤的情形,這時,遞歸也就終止了,問題也得到了解決。通過以上分析,這里有很明顯的遞歸關系。

由此,可以給出漢諾塔問題的遞歸算法如下:

1. 當僅有1個盤子時,把這個盤子從A塔柱移動到C塔柱上

2. 當圓盤的個數多于1個時,如下解決:

(1) 先將A塔柱上的(n-1)個圓盤通過C塔柱移動到B塔柱上

(2) 再將A塔柱上的第n個圓盤直接移動到C塔柱上

(3) 最后B塔柱上的(n-1)個圓盤通過A塔柱移動到C塔柱上

該算法對應的代碼如下:

void hanoi(int n,char a,char b,char c)

{

if(n==1){

cout<<a<<"->"<<c<<endl;

}

else {

hanoi(n-1,a,c,b);

cout<<a<<"->"<<c<<endl;

hanoi(n-1,b,a,c);

}

}

        三、 非遞歸解法及其C++實現

首先把三根柱子按順序排成品字型,把所有的圓盤按從大到小的順序放在柱子A上。

根據圓盤的數量確定柱子的排放順序:若n為偶數,按順時針方向依次擺放 A B C;

(1)若n為奇數,按順時針方向依次擺放 A C B。

按順時針方向把圓盤1從現在的柱子移動到下一根柱子,即當n為偶數時,若圓盤1在柱子A,則把它移動到B;若圓盤1在柱子B,則把它移動到C;若圓盤1在柱子C,則把它移動到A。

(2)接著,把另外兩根柱子上可以移動的圓盤移動到新的柱子上。

即把非空柱子上的圓盤移動到空柱子上,當兩根柱子都非空時,移動較小的圓盤
這一步沒有明確規定移動哪個圓盤,你可能以為會有多種可能性,其實不然,可實施的行動是唯一的。

(3)反復進行(1)(2)操作,最后就能按規定完成漢諾塔的移動[3]

C++代碼如下:

const int MAX = 64; //圓盤的個數最多為64

struct st{  //用來表示每根柱子的信息

      int s[MAX]; //柱子上的圓盤存儲情況

      int top; //棧頂,用來最上面的圓盤

      char name; //柱子的名字,可以是A,B,C中的一個

     

      int Top(){//取棧頂元素

            return s[top];

      }

      int Pop(){//出棧

            return s[top--];

      }

      void Push(int x){//入棧

            s[++top] = x;

      }

} ;

long Pow(int x, int y); //計算x^y
void Creat(st ta[], int n); //給結構數組設置初值
void Hannuota(st ta[], long max); //移動漢諾塔的主要函數

int main(void)
{

      int n;
      cin >> n; //輸入圓盤的個數      
      st ta[3]; //三根柱子的信息用結構數組存儲
      Creat(ta, n); //給結構數組設置初值

      long max = Pow(2, n) - 1;//動的次數應等于2^n - 1
      Hannuota(ta, max);//移動漢諾塔的主要函數

      system("pause");
      return 0;
}

void Creat(st ta[], int n)
{

      ta[0].name = 'A';

      ta[0].top = n-1;

      for (int i=0;i<n;i++)//把所有的圓盤按從大到小的順序放在柱子A上

            ta[0].s[i] = n - i;            
      ta[1].top = ta[2].top = 0;//柱子B,C上開始沒有沒有圓盤
      for (int i=0; i<n; i++)

            ta[1].s[i] = ta[2].s[i] = 0;

           

      if (n%2 == 0) //若n為偶數,按順時針方向依次擺放 A B C
      {
            ta[1].name = 'B';
            ta[2].name = 'C';
      }
      else  //若n為奇數,按順時針方向依次擺放 A C B
      {
            ta[1].name = 'C';
            ta[2].name = 'B';
      }
}

long Pow(int x, int y)
{
      long sum = 1;
      for (int i=0; i<y; i++)
            sum *= x;

      return sum;
}

void Hannuota(st ta[], long max)
{
      int k = 0; //累計移動的次數
      int i = 0;
      int ch;
      while (k < max)
      {
            //按順時針方向把圓盤1從現在的柱子移動到下一根柱子
            ch = ta[i%3].Pop();
            ta[(i+1)%3].Push(ch);
            cout << ++k << ": " << "Move disk " << ch << " from " << ta[i%3].name << " to " << ta[(i+1)%3].name << endl;
            i++;
            //把另外兩根柱子上可以移動的圓盤移動到新的柱子上
            if (k < max)
            {//把非空柱子上的圓盤移動到空柱子上,當兩根柱子都為空時,移動較小的圓盤
                  if (ta[(i+1)%3].Top() == 0 || ta[(i-1)%3].Top() > 0 && ta[(i+1)%3].Top() > ta[(i-1)%3].Top())
                  {
                        ch =  ta[(i-1)%3].Pop();
                        ta[(i+1)%3].Push(ch);
                        cout << ++k << ": " << "Move disk " << ch << " from " << ta[(i-1)%3].name << " to " << ta[(i+1)%3].name << endl;
                  }
                  else
                  {
                        ch =  ta[(i+1)%3].Pop();
                        ta[(i-1)%3].Push(ch);
                        cout << ++k << ": " << "Move disk " << ch << " from " << ta[(i+1)%3].name << " to " << ta[(i-1)%3].name << endl;
                  }
            }
      }
}

       四、 比較與總結

漢諾塔問題是經典的遞歸算法例子。本文給出了遞歸和非遞歸兩種解決漢諾塔問題的算法。遞歸的漢諾塔問題解法優點是邏輯清晰,易于理解,可讀性好,算法語句少,也有助于理解遞歸的思想,但需要大量的棧空間,運行效率不高。如果用非遞歸方法解決漢諾塔問題的算法,優點是僅用少量的存儲空間克服了遞歸算法的不足,兼顧了時間與空間的效率,缺點是理解起來可能比較困難,可讀性也不是很好,在實際中,可根據需要選擇一種進行解決。

 

   參考文獻

[1] 王春森.程序員教程[M]. 北京:清華大學出版社,2001-05

[2] 周敏. 漢諾塔問題的算法分析及C語言實現[j].電腦學習,2009年10月

[3] 談祥柏 著.數學營養菜[M]. 中國少年兒童出版社,2004-5-1