參考:
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為任何數字的時候都可以實現這一功能。