遞迴3: 漢諾塔的遞迴與迭代實現
阿新 • • 發佈:2019-01-30
遞迴實現與main():
執行結果:
/*------------------------------------------------------ 漢諾塔主要是有三個塔座X,Y,Z,要求將從小到大編號為 1,2.....n 的 圓盤從X移動到塔座Z上,要求 (1):每次只能移動一個圓盤 (2):圓盤可以插到X,Y,Z中任一塔座上 (3):任何時候不能將一個較大的圓盤壓在較小的圓盤之上 初始:所有圓盤都在 X 塔座,並且最大的圓盤在最底部,然後是次大的; 結束:所有圓盤都在 Z 塔座,並且最大的圓盤在最底部,然後是次大的; ------------------------------------------------------*/ #include <iostream> #include <ctime> // time() using namespace std; /*----------------------------------------------------------- 前置條件: n > 0 後置條件: 輸出將n個圓盤從源塔座(orig)移動到目的塔座(dest)的步驟, 臨時塔座(temp)用於臨時存放。 演算法(遞迴): n == 1時,把盤1從源移動到目的地 n > 1時, 1)將n-1個圓盤(每次移動一個)從源移動到臨時塔座 2)將盤n從源移動到目的地 3)將n-1個圓盤(每次移動一個)從臨時塔座移動到目的塔座 時間複雜度: wotstTime(n) 是 O(2^n). 空間複雜度: worstSpace(n) 是 O(n). -----------------------------------------------------------*/ void move(int n, char orig[], char dest[], char temp[]) { static char ori = 'X', tem = 'Y', des = 'Z', ch; // 三個塔座 static int num = 0, length = n; // num記錄移動步數, length儲存圓盤數目 if (n <= 0){ // 處理引數傳遞錯誤 cout << "The value is error!\n"; return; } if (n == 1){ // 通用策略: n == 1時情況 dest[n-1] = orig[n-1]; num++; cout << num << ") Disc " << orig[n-1] << ": " << ori << "-->" << des << endl; } else{ // 通用策略: n > 1時情況 ch = des; des = tem; tem = ch; move(n-1, orig, temp, dest); ch = des; des = tem; tem = ch; dest[n-1] = orig[n-1]; num++; cout << num << ") Disc " << orig[n-1] << ": " << ori << "-->" << des << endl; ch = ori; ori = tem; tem = ch; move(n-1, temp, dest, orig); ch = ori; ori = tem; tem = ch; } if (dest[length] != '\0') dest[length] = '\0'; } // move中宣告的區域性變數為靜態的,這樣在使用遞迴呼叫的move函式裡只在第一次定義時初始化; // 塔座名ori、tem、des要隨著遞迴呼叫時引數的傳遞而變化,遞迴呼叫結束後應該及時恢復; // n為圓盤數目,塔座結構採用C-styel字串,因注意字串末尾要有'\0'; void movecopy(int, char [], char [], char []); int main() { long start_time, finish_time, elapsed_time1, elapsed_time2; const int N = 5; // 圓盤數目為 N-1 char orig[N] = "abcd", temp[N], dest[N]; // 計算move()函式運行了多少時間(time()的返回值型別為long) cout << "move::orig = " << orig << endl; start_time = time(NULL); move(N-1, orig, dest, temp); finish_time = time(NULL); elapsed_time1 = finish_time - start_time; cout << "move::dest = " << dest << endl << endl; // 計算movecopy()函式運行了多少時間 cout << "movecopy::orig = " << orig << endl; start_time = time(NULL); movecopy(N-1, orig, dest, temp); finish_time = time(NULL); elapsed_time2 = finish_time - start_time; cout << "movecopy::dest = " << dest << endl << endl; // move()和movecopy()執行時間比較 cout << "move::The elapsed time was " << elapsed_time1 << " senonds." << endl; cout << "movecopy::The elapsed time was " << elapsed_time2 << " senonds." << endl; return 0; }
迭代實現:
/*-------------------------------------------------------------- 前置條件: n > 0 後置條件: 輸出將n個圓盤從源塔座(orig)移動到目的塔座(dest)的步驟, 臨時塔座(temp)用於臨時存放。 演算法(迭代): 1)確定哪一個圓盤要移動。 a.移動次數: c(n) = 2^n -1; b.m為n位的二進位制數,則m的取值範圍為0~2^n -1。 規律:讓m每次遞增1,可以發現,m中最高一位的剛剛由0變為1的位置的位置編號, 和即將要移動的盤子編號有確定關係。 2)這個盤子往哪個塔座上移動。 a.n為奇數時,奇數編號圓盤按順時針移動(X->Y->Z->X),偶數編號圓盤按逆時針 移動(X->Z->Y->X); b.n為偶數時,偶數編號圓盤按順時針移動(X->Y->Z->X),奇數編號圓盤按逆時針 移動(X->Z->Y->X); --------------------------------------------------------------*/ void movecopy(int n, char orig[], char dest[], char temp[]) { char ori[] = "orig(X)", tem[] = "temp(Y)", des[] = "dest(Z)";// 三個塔座 int num = 0; // num記錄移動步數 int m1[32], m2[32], tempnum, i, j, k; // m1、m2陣列儲存 n的二進位制bit位,且 n(m2對應) = n(m1對應) + 1; // i、j分別為m1、m2中儲存n值要求的二進位制的最小bit數; // k+1 為求得的盤子編號 int x = n, y = 0, z = 0; // x、y、z分別跟蹤三個塔座當前的圓盤數目 if (n == 0){ // 處理引數傳遞錯誤 cout << "The value is error!\n"; return; } // 儲存orig中元素到ch[32],然後反轉orig塔座中圓盤的順序 // (如:'a'在orig中第一個位置,但是 'a' 也在塔座的最上方--第n-1個位置) char ch[32]; for (i = 0; i < n; i++) ch[i] = orig[i]; for (int length = n-1, i = 0; i < n; i++, length--){ orig[i] = ch[length]; } ch[n] = '\0'; dest[n] = '\0'; temp[n] = '\0'; for (int l = 0; l < (1<<n)-1; l++) // 移動次數:2^n-1 既是 (1<<n)-1 { // 1)確定哪一個圓盤要移動(求 k+1 的值) tempnum = l; for (i = 0; tempnum != 1 && tempnum != 0; i++) {m1[i] = tempnum%2; tempnum = tempnum/2;} m1[i] = tempnum; tempnum = l+1; for (j = 0; tempnum != 1 && tempnum != 0; j++) {m2[j] = tempnum%2; tempnum = tempnum/2;} m2[j] = tempnum; if (j > i) k = j; else for (k = j; m2[k] == m1[k]; k--) ; num++; cout << num << ") Disc " << ch[k] << ": "; // 2)這個盤子往哪個塔座上移動。 if (((n % 2 == 0 && ((k+1)%2 == 0))) || // (n為偶數,偶數編號圓盤順時針移動) ((n % 2 != 0) && ((k+1)%2 != 0))){ // (n為奇數,奇數編號圓盤順時針移動) for (i = 0; ch[k] != orig[i] && i < x; i++) ; if ((i > 0 && i < x) || (i == 0 && orig[i] == ch[k])){ dest[y] = orig[i]; y++; x--; cout << ori << "-->" << des; orig[i] = '\0'; } else{ // (如果在orig塔座沒有找到需要圓盤) for (i = 0; ch[k] != dest[i] && i < y; i++) ; if ((i > 0 && i < y) || (i == 0 && dest[i] == ch[k])){ temp[z] = dest[i]; z++; y--; cout << des << "-->" << tem; dest[i] = '\0'; } else{ // (如果在orig和dest塔座都沒有找到需要圓盤) for (i = 0; ch[k] != temp[i] && i < z; i++) ; if ((i > 0 && i < z) || (i == 0 && temp[i] == ch[k])){ orig[x] = temp[i]; x++; z--; cout << tem << "-->" << ori; temp[i] = '\0'; } } } } else{ // (n為奇數,偶數編號圓盤逆時針移動)(n為偶數,奇數編號圓盤逆時針移動) for (i = 0; ch[k] != orig[i] && i < x; i++) ; if ((i > 0 && i < x) || (i == 0 && orig[i] == ch[k])){ temp[z] = orig[i]; z++; x--; cout << ori << "-->" << tem; orig[i] = '\0'; } else{ // (如果在orig塔座沒有找到需要圓盤) for (i = 0; ch[k] != dest[i] && i < y; i++) ; if ((i > 0 && i < y) || (i == 0 && dest[i] == ch[k])){ orig[x] = dest[i]; x++; y--; cout << des << "-->" << ori; dest[i] = '\0'; } else{ // (如果在orig和dest塔座都沒有找到需要圓盤) for (i = 0; ch[k] != temp[i] && i < z; i++) ; if ((i > 0 && i < z) || (i == 0 && temp[i] == ch[k])){ dest[y] = temp[i]; y++; z--; cout << tem << "-->" << des; temp[i] = '\0'; } } } } cout << endl; } // 儲存dest中元素到ch[32],然後反轉dest塔座中圓盤的順序 for (i = 0; i < n; i++) ch[i] = dest[i]; for (int length = n-1, i = 0; i < n; i++, length--){ dest[i] = ch[length]; } }
執行結果:
我把main()中變動為 const int N = 15; 時,move()運行了 44 seconds,而movecopy()運行了 80 seconds..
這個遞迴版本明顯好於迭代版本