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


目錄

1 問題描述

2 解決方案 

2.1 遞歸法

2.2 非遞歸法

 

 


1 問題描述

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

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

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

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

 

相關經典題目延伸:

引用自百度百科:

有三根相鄰的柱子,標號為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實現)

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM