算法筆記_013:漢諾塔問題(Java遞歸法和非遞歸法)

目錄java

1 問題描述
數組

2 解決方案 spa

2.1 遞歸法
.net

2.2 非遞歸法
翻譯

 

 


1 問題描述

Simulate the movement of the Towers of Hanoi Puzzle; Bonus is possible for using animation.code

e.g. if n = 2 ; AB ; AC ; BC; blog

      if n = 3; AC ; AB ; CB ; AC ; BA ; BC ; AC;遞歸

翻譯:模擬漢諾塔問題的移動規則;得到獎勵的移動方法仍是有可能的。get

 

相關經典題目延伸:animation

引用自百度百科:

有三根相鄰的柱子,標號爲A,B,CA柱子上從下到上按金字塔狀疊放着n個不一樣大小的圓盤,要把全部盤子一個一個移動到柱子C上,而且每次移動同一根柱子上都不能出現大盤子在小盤子上方,請問至少須要多少次移動,設移動次數爲H(n)。

 

首先咱們確定是把上面n-1個盤子移動到柱子B上,而後把最大的一塊放在C上,最後把B上的全部盤子移動到C上,由此咱們得出表達式:

H⑴ = 1          A>C

H(2) = 3         A>BA>CB>C

H(3) = 7         ...

H(4) = 15       

... ...

H(n) = 2*H(n-1+1 (n>1

那麼咱們很快就能獲得H(n)的通常式:

H(n) = 2^n - 1 (n>0)

 

 


2 解決方案

2.1 遞歸法

 1 import java.util.Scanner;
 2 
 3 public class Hanoi {
 4     
 5     //使用遞歸法求解含有n個不一樣大小盤子的漢諾塔移動路徑,參數n爲盤子數,把A塔上盤子所有移動到C塔上,B爲過渡塔
 6     public static void recursionHanoi(int n,char A,char B,char C){
 7         if(n == 1){
 8             System.out.print(A+"——>"+C+"\n");    
 9         }
10         else{
11             recursionHanoi(n-1,A,C,B);         //使用遞歸先把A塔最上面的n-1個盤子移動到B塔上,C爲過渡塔
12             System.out.print(A+"——>"+C+"\n");       //把A塔中底下最大的圓盤,移動到C塔上
13             recursionHanoi(n-1,B,A,C);         //使用遞歸把B塔上n-1個盤子移動到C塔上,A爲過渡塔
14         }
15     }
16 
17    public static void main(String[] args){
18         System.out.println("請輸入盤子總數n:");
19         Scanner in = new Scanner(System.in);
20         int n = in.nextInt();    
21         recursionHanoi(n,'A','B','C');    
22     }
23 }

 

運行結果:

請輸入盤子總數n:
2
A——>B
A——>C
B——>C
請輸入盤子總數n:
3
A——>C
A——>B
C——>B
A——>C
B——>A
B——>C
A——>C

 

 

2.2 非遞歸法

要使用非遞歸方法,首先要解決的核心問題就是找到漢諾塔的移動規律。如今問題是把n個盤子從A塔所有移動到C塔,那麼先看看n = 234時,其具體盤子的移動路徑結果(PS:移動路徑前標號是指A塔上原有的第幾個盤子,如(1)表明A塔上原有最上面的那個盤子,依次類推...):

 

n = 21)A—>B

2)A—>C

1)B—>C

n = 31)A——>C

(2)A——>B

1)C——>B

3)A——>C

1)B——>A

(2)B——>C

1)A——>C

n = 4:

1)A——>B

2)A——>C

1)B——>C

(3)A——>B

1)C——>A

2)C——>B

1)A——>B

4)A——>C

1)B——>C

2)B——>A

1)C——>A

(3)B——>C

1)A——>B

2)A——>C

1)B——>C

 

從上咱們能夠發現,n爲偶數24時路徑前標號爲(1)的盤子移動路徑依次爲A——>B——>C,A——>B——>C——>A——>B——>C。n爲偶數24時路徑前標號爲(2)的盤子移動路徑依次爲A>C,A>C——>B——>A>C。並且發現n = 4其標號爲(1)和標號爲(3)的移動路徑如出一轍。n爲奇數3時路徑前標號爲(1)和(2)的盤子移動路徑依次爲A——>C——>B——>A——>C,A——>B——>C。

 

看到這裏,咱們能夠大膽猜想盤子的具體移動路徑與盤子的總個數的奇偶性以及盤子標號的奇偶性有關,並且移動的路徑是固定又循環的。

 

那麼如今設定一個二維數組用來存放盤子下次移動的塔:

char next = new char[2][3];

二維數組中行char[0]表明數組下標爲偶數的盤子下次要移動的塔

二維數組中行char[1]表明數組下標爲奇數的盤子下次要移動的塔

二維數組重列char[0][0]表明盤子如今在A塔準備進行下次移動

二維數組重列char[0][1]表明盤子如今在B塔準備進行下次移動

二維數組重列char[0][2]表明盤子如今在C塔準備進行下次移動

 

那麼下面咱們就來根據盤子如今所在塔,設定其下次移動的目的塔(PS:設共有n的盤子)

 

if(n爲偶數)

{

   //數組下標爲偶數的盤子移動目的塔,注意上面示例的標號爲(1),其數組下標爲0

   next[0][0] = ‘B’;   //看n = 4的移動路徑中(1)A——>B

   next[0][1] = ‘C’;   //看n = 4的移動路徑中(1)B——>C

   next[0][2] = ‘A’;   //看n = 4的移動路徑中(1)C——>A

   //數組下標爲奇數的盤子移動目的塔

   next[1][0] = ‘C’;   //看n = 4的移動路徑中(2)A——>C

   next[1][1] = ‘A’;   //看n = 4的移動路徑中(2)B——>A

   next[1][0] = ‘B’;   //看n = 4的移動路徑中(2)C——>B

}

If(n爲奇數)

{

   //數組下標爲偶數的盤子移動目的塔,注意上面示例的標號爲(1),其數組下標爲0

   Next[0][0] = ‘C’;   //看n = 3的移動路徑中(1)A——>C

   Next[0][1] = ‘A’;   //看n = 3的移動路徑中(1)B——>A

   Next[0][2] = ‘B’;   //看n = 3的移動路徑中(1)C——>B

   //數組下標爲奇數的盤子移動目的塔

   Next[1][0] = ‘B’;   //看n = 3的移動路徑中(2)A——>B

   Next[1][1] = ‘C’;   //看n = 3的移動路徑中(2)B——>C

   Next[1][2] = ‘A’;   //此處根據觀察規律假設的

}

 

到這裏,距離使用非遞歸法解決漢諾塔問題已經有頭緒了,此處還有注意一點就是H(n) = 2^n - 1 (n>0),即移動n個盤子須要總次數爲2^n - 1 ,即便用非遞歸法是須要進行循環2^n - 1 次。

 

 1 package com.liuzhen.ex2;
 2 
 3 import java.util.Scanner;
 4 
 5 public class Hanoi {
 6     
 7     //使用遞歸法求解含有n個不一樣大小盤子的漢諾塔移動路徑,參數n爲盤子數,把A塔上盤子所有移動到C塔上,B爲過渡塔
 8     public static void recursionHanoi(int n,char A,char B,char C){
 9         if(n == 1){
10             System.out.print(A+"——>"+C+"\n");    
11         }
12         else{
13             recursionHanoi(n-1,A,C,B);         //使用遞歸先把A塔最上面的n-1個盤子移動到B塔上,C爲過渡塔
14             System.out.print(A+"——>"+C+"\n");       //把A塔中底下最大的圓盤,移動到C塔上
15             recursionHanoi(n-1,B,A,C);         //使用遞歸把B塔上n-1個盤子移動到C塔上,A爲過渡塔
16         }
17     }
18     
19     public static void noRecursionHanoi(int n){
20         if(n<=0){
21             throw new IllegalArgumentException("n must be >=1");
22         }
23         char[] hanoiPlate = new char[n];   //記錄n個盤子所在的漢諾塔(hanoiPlate[1]='A'意味着第二個盤子如今在A上)
24         char[][] next = new char [2][3];   //盤子下次會移動到的盤子的可能性分類
25         int[] index = new int[n];
26 
27         //根據奇偶性將盤子分爲兩類
28         for(int i=0;i<n;i=i+2){
29             index[i]=0;
30         }
31         for(int i=1;i<n;i=i+2){
32             index[i]=1;
33         }
34 
35         //一開始全部盤子都在A上
36         for(int i=0;i<n;i++){
37             hanoiPlate[i]='A';
38         }
39 
40         //n的奇偶性對移動方式的影響
41         if(n%2==0){
42             //數組下標爲偶數的盤子移動目的塔,注意上面示例的標號爲(1),其數組下標爲0
43             next[0][0]='B';
44             next[0][1]='C';
45             next[0][2]='A';
46             //數組下標爲奇數的盤子移動目的塔
47             next[1][0]='C';
48             next[1][1]='A';
49             next[1][2]='B';
50         }
51         else
52         {
53             //數組下標爲偶數的盤子移動目的塔,注意上面示例的標號爲(1),其數組下標爲0
54             next[0][0]='C';
55             next[0][1]='A';
56             next[0][2]='B';
57             //數組下標爲奇數的盤子移動目的塔
58             next[1][0]='B';
59             next[1][1]='C';
60             next[1][2]='A';
61         }
62 
63         //開始移動
64         for(int i=1;i<(1<<n);i++){                  //總共要執行2^n-1(1<<n-1)步移動
65             int m=0;                                //m表明第m塊盤子hanoiPlate[m]
66 
67             //根據步驟數i來判斷移動哪塊盤子以及如何移動
68             for(int j=i;j>0;j=j/2){
69                 if(j%2!=0){    //此步驟光看代碼代碼有點抽象,建議手動寫一下n = 2時的具體移動路徑的j、m值變化
70                     System.out.println("("+(m+1)+")"+hanoiPlate[m]+"->"+next[index[m]][hanoiPlate[m]-'A']);
71                     hanoiPlate[m]=next[index[m]][hanoiPlate[m]-'A'];
72                     break;                           //移動盤子後則退出這層循環
73                 }
74                 m++;
75             }
76         }
77     }
78 
79     public static void main(String[] args){
80         System.out.println("請輸入盤子總數n:");
81         Scanner in = new Scanner(System.in);
82         int n = in.nextInt();    
83         recursionHanoi(n,'A','B','C');    
84         System.out.println("非遞歸法結果:");
85         noRecursionHanoi(n);    
86         System.out.println();    
87     }
88 }

 

 

運行結果:

請輸入盤子總數n:
2
A——>B
A——>C
B——>C
非遞歸法結果:
(1)A->B
(2)A->C
(1)B->C

請輸入盤子總數n:
3
A——>C
A——>B
C——>B
A——>C
B——>A
B——>C
A——>C
非遞歸法結果:
(1)A->C
(2)A->B
(1)C->B
(3)A->C
(1)B->A
(2)B->C
(1)A->C

 

 參考資料:

   1. (原創)Hanoi塔問題的遞歸方法與非遞歸方法(java實現)

相關文章
相關標籤/搜索