Hanoi塔問題是源於印度一個古老傳說的益智玩具。設a,b,c是三個塔座,開始時,在塔座a上有一疊共n個圓盤,這些圓盤自上而下,由大到小疊在一起,各圓盤的編號為1,2,3,...,n。現要求將塔座a上的這一疊圓盤移動到塔座b上,並仍按從到到小的順序疊置。再移動圓盤時應該遵守以下移動規則:
規則一:每次只能移動一個圓盤。
規則二:不允許將較大的圓盤壓在較小的圓盤上面。
規則三:在滿足規則一、規則二的情況下,可將圓盤移動到啊a,b,c中任一塔座上。
如圖為一Hanoi塔問題的移動步驟:
a
圓盤總數為n,當n=1時,只要將編號為1的圓盤從塔座a直接移動到塔座b。當n>1時,需要利用塔座c作為輔助,將n-1個較小的圓盤依照移動規則從塔座a移動至塔座c,然后將最大的圓盤從塔座a移動至塔座b,再將n-1個圓盤從塔座c移動至塔座b,T(n)=2^(n-1)-1。由於n-1個圓盤以同樣的方式移動了兩次,可以利用遞歸方法來解決。這個就是典型的Hanoi塔遞歸問題。
public static void hanoi(int n,int a,int b,int c){ if(n>0){ hanoi(n-1,a,c,b); move(a,b); hanoi(n-1,c,b,a); } }
如果將三個塔座a,b,c改為四個塔座a,b,c,d,利用分治法則和歸納法分成兩個小規模問題。
T(n)來表示最小步數,可以知道T(1)=1,T(2)=3,當n>=3時,假設已經輸出T(1),T(2),…,T(n-1),則T(n)可以這樣計算:
for i=1;i<n;i++
一:先從塔座a移動i個圓盤到塔座d(b,c),塔座b,c作為輔助,移動步數為T(i)。
二:剩下的n-i個圓盤只可以在三個塔座之間移動,利用Hanoi函數將其由塔座a移動至塔座,塔座c作為輔助,移動步數為T(n-i)=2^(n-i)-1。
三:再將塔座d中的i個圓盤移動至塔座b,塔座c,a作為輔助(與步一移動方式相同)移動步數為T(i)。
public class Han { //創建一個靜態數組用於存儲移動的圓盤的最小步數 static int[] a=new int [65]; //創建一個靜態數組用於存儲先移動的圓盤的個數 static int[] s=new int [65]; public static void step() { a[0] = 0; a[1] = 1; a[2] = 3; //先移動圓盤個數的循環 for (int n = 3; n <= 64; n++) { double min = 200000; double temp = 0; int temp2 = 0; //一個一個移動計算步數和圓盤個數 for (int i = 1; i < n; i++) { temp = 2 * a[i] + Math.pow(2, n - i) - 1; if (temp < min) { min = temp;//計算出移動總步數並賦值 temp2 = i;//計算出先移動圓盤數並賦值 } a[n] = (int) temp; s[n] = temp2; } } }
public static void hanoi4(int n,int a,int b,int c,int d,int []s){ if(n>=3){ hanoi4(s[n],a,b,c,d,s); hanoi(n-s[n],a,c,b); hanoi4(s[n],d,a,b,c,s); }else{ if(n==1){ move(a,b); } if(n==2){ hanoi(2,a,c,b); } } }
示例:
在Hanoi塔問題中如果塔的個數變為a,b,c,d四個,現要將n個圓盤從a全部移動到d,移動規則不變,編寫移動步數最小的方案和具體的移動過程(只考慮n<=64),並演示當n=9時的情形。
package jihe; /** * author Gsan */ public class Han { //創建一個靜態數組用於存儲移動的圓盤的最小步數 static int[] a=new int [65]; //創建一個靜態數組用於存儲先移動的圓盤的個數 static int[] s=new int [65]; public static void step() { a[0] = 0; a[1] = 1; a[2] = 3; //先移動圓盤個數的循環 for (int n = 3; n <= 64; n++) { double min = 200000; double temp = 0; int temp2 = 0; //一個一個移動計算步數和圓盤個數 for (int i = 1; i < n; i++) { temp = 2 * a[i] + Math.pow(2, n - i) - 1; if (temp < min) { min = temp;//計算出移動總步數並賦值 temp2 = i;//計算出先移動圓盤數並賦值 } a[n] = (int) temp; s[n] = temp2; } } } public static void hanoi(int n,int a,int b,int c){ if(n>0){ hanoi(n-1,a,c,b); move(a,b); hanoi(n-1,c,b,a); } } public static void move(int a,int b){ System.out.println("move"+a+"to"+b); } public static void hanoi4(int n,int a,int b,int c,int d,int []s){ if(n>=3){ hanoi4(s[n],a,b,c,d,s); hanoi(n-s[n],a,c,b); hanoi4(s[n],d,a,b,c,s); }else{ if(n==1){ move(a,b); } if(n==2){ hanoi(2,a,c,b); } } } public static void main(String[] args) { int n=9; step(); hanoi4(n,1,2,3,4,s); System.out.println("9個圓盤移動的最小步數為:"+a[n]); } }
運行結果: