7-17 漢諾塔的非遞歸實現 (25分)


參考:

https://blog.csdn.net/computerme/article/details/18080511的和https://zhuanlan.zhihu.com/p/36085324的。

簡單說一下我理解到的方法吧

 

第一步是判斷輸入的n是奇數還是偶數,若為奇數,則按順時針以ACB的順序擺成品字型,若為偶數,則按順時針以ABC的順序擺成品字型。(參考下圖)

 

 

第二步將序號為1(最小)的盤,按順時針放到下一個字母。假如以ABC順序順時針擺放時,若1盤在A,則將它移動到B,若在B則移動到C……

第三步處理剩余兩個字母(1盤此時不在的那兩個字母),暫且稱為X和Y。可能會出現如下幾種情況:

1)X和Y都有盤,此時他們頂層的盤必能比較出大小,那么這時候將小的盤移動到大的盤上面,結束。

 

2)X和Y都沒盤,不用做任何處理。(比如輸入的n為1的時候就會出現這種情況)

 

3)X和Y一個有盤,一個沒盤,這時候將有盤的一邊的盤移動到沒有盤的一邊,結束。

 

只要我們循環運行第二步第三步就能完成漢諾塔的非遞歸實現。

下面是我的AC代碼:

  1 //非遞歸AC代碼
  2 //用cout最后一個測試會超時,改為printf就AC了
  3
  4 #include <iostream>
  5 #include <string>
  6 #include <cstring>
  7 #include <stdio.h>
  8 #include <cmath>
  9 using namespace std;
 10 int n;//輸入的盤子數
 11 
 12 class stack_//“三根柱子”的類型
 13 {
 14 public:
 15     stack_() :r(0){}
 16     ~stack_() {}
 17     void push(int k)
 18     {
 19         h[++r] = k;
 20     }
 21     int pop()
 22     {
 23         if (r <= 0)
 24             return 0;
 25         else
 26         return h[r--];
 27     }
 28 public:
 29     char name;
 30     int r;//指針
 31     int h[10000];
 32 };
 33 stack_ S[3];//將三根柱子定義為數組,方便操作
 34 
 35 bool is_over(int cnt)//判斷移動次數有沒有超過2^n-1
 36 {
 37     if (cnt >= pow(2, n)-1)
 38         return true;
 39     else
 40         return false;
 41 }
 42 
 43 void move(char a, char b, char c)//移動函數主體
 44 {
 45     int pop_x,temp1,temp2;
 46     int cnt = 0;//累計移動的次數
 47     int i = 0;//循環的次數
 48     
 49     while(cnt < pow(2,n)-1 )//移動次數到達最大時就要退出循環,繼續循環會導致錯誤
 50     {
 51         int k;//中間變量,簡化式子
 52         pop_x = S[i % 3].pop();//隨着i的變化,可以實現對S[0],S[1],S[2]輪回判斷
 53         if (pop_x == 1)
 54         {
 55             k = i + 1;
 56             S[k % 3].push(1);
 57             if (!is_over(cnt))
 58             {
 59                  //cout << S[i % 3].name << " -> " << S[k% 3].name << endl;
 60                 printf("%c -> %c\n", S[i % 3].name, S[k % 3].name);
 61                     cnt++;
 62             }
 63             temp1 = S[(k + 1) % 3].pop();
 64             temp2 = S[(k - 1) % 3].pop();
 65             if ((temp1 != 0 && temp2 != 0)&& (temp1 < temp2)  || temp2 == 0 and temp1 != 0)//temp1 移動到 temp2 的情況
 66             {
 67                 S[(k - 1) % 3].push(temp2);
 68                 S[(k - 1) % 3].push(temp1);
 69                 if (!is_over(cnt))
 70                 {
 71                     //cout << S[(k + 1) % 3].name << " -> " << S[(k - 1) % 3].name <<endl;
 72                     printf("%c -> %c\n", S[(k+1) % 3].name, S[(k-1) % 3].name);
 73                     cnt++;
 74                 }
 75             
 76             }
 77             else if (temp1 == 0 && temp2 == 0)
 78             {
 79                 //不移動任何盤子,只要把剛剛出棧的元素重新壓回去
 80                 S[(k + 1) % 3].push(temp1);
 81                 S[(k - 1) % 3].push(temp2);
 82             }
 83             else
 84             {
 85                 S[(k + 1) % 3].push(temp1);
 86                 S[(k + 1) % 3].push(temp2);
 87                 if (!is_over(cnt))
 88                 {
 89                     //cout << S[(k - 1) % 3].name << " -> " << S[(k + 1) % 3].name << endl;
 90                     printf("%c -> %c\n", S[(k-1)% 3].name, S[(k+1) % 3].name);
 91                     cnt++;
 92                 }
 93             }
 94             i++;//注意在末尾將i的值加1,實現0,1,2的輪回
 95         }
 96         else
 97         {
 98             S[i % 3].push(pop_x);//不符合條件,重新壓回棧
 99             i++;
100         }
101     }
102     //cout << endl << cnt << endl;
103 }
104 
105 
106 void hanoi(int n, char a, char b, char c)//接口
107 {
108     S[0].name = a,
109     S[1].name = b;
110     S[2].name = c;
111     for (int i = n; i >= 1; i--)//從大到小將盤子壓入棧
112     {
113         S[0].push(i);
114     }
115        move(a, b, c);//調用move開始進行移動
116 }
117 
118 int main()
119 {
120    cin >> n;
121     if (n % 2 == 0)
122         hanoi(n, 'a', 'b', 'c');//偶數的時候按abc順序
123     else
124         hanoi(n, 'a', 'c', 'b');//奇數的時候按acb順序
125     return 0 ;
126 }

 

再附加個遞歸實現的:

 1 //遞歸實現
 2 #include <iostream>
 3 #include <string>
 4 #include <cstring>
 5 
 6 using namespace std;
 7 void hanoi(int n, char a, char b, char c)
 8 {
 9     if(n==1)
10     {
11         cout << a <<" -> " << c << endl;
12         return;
13     }
14     else
15     {
16         hanoi(n-1,a,c,b);
17         cout << a <<" -> " << c <<endl;
18         hanoi(n-1,b,a,c);
19         return;
20     }
21 }
22 
23 int main()
24 {
25     int n ;
26     cin >> n;
27     hanoi(n,'a','b','c');
28     return 0;
29 }

對於遞歸算法我的個人理解:
首先將初始盤子看成是n-1的整體和最下面的最大一塊的組合體。

hanoi(n-1,a,c,b);
cout << a <<" -> " << c <<endl;
hanoi(n-1,b,a,c);

hanoi(n,A,B,C,)中A為初始位置,C為目標位置,上述代碼第一步目的是將n-1整體從A移動到B,所以調用hanoi(n-1,A,C,B)。依此類推,第二步是要把最大一塊從A移動到C,所以也可寫成hanoi(1,A,B,C),第三步同理。

 那么為什么hanoi(n-1,A,B,C)可以實現將上面的n-1個盤從A移動到B呢?

我的理解:

當n=2時,即n-1為1的時候,這個函數顯然是可以實現這一目標的。當n=3時n-1就為2了,這個時候調用hanoi(n-1,A,B,C)其實就是調用hanoi(2,A,B,C),那么我們剛剛已經確定參數為2的時候是可以達到目的的,那么可以推出,n為3的時候也可以達到目的(因為他是借助n-1=2時的函數實現的),於是就可以繼續往后面推斷出n為任何數字的時候都可以實現這一功能。

 

 

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM