1. 程式人生 > >遞迴3: 漢諾塔的遞迴與迭代實現

遞迴3: 漢諾塔的遞迴與迭代實現

遞迴實現與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..

這個遞迴版本明顯好於迭代版本