修改漢諾塔問題的遊戲規則:限制不能從最左側的塔直接移動到最右側,也不能從最右側直接移動到最左側,而是必須通過中間。求當塔有N層的時候,打印最優移動和最優移動總步數。java
要求:ide
- 方法一:遞歸的方法
- 方法二:非遞歸的方法,用棧來模擬漢諾塔的三個塔
首先,若是隻剩最上層的塔須要移動,則有以下處理:code
以上就是遞歸的終止條件,也就是隻剩上層塔時的打印過程。遞歸
多層塔的時候。遊戲
若是剩下N層塔,從最上到最小依次爲1~N-1,則有以下判斷部署
若是剩下的N層塔都在左,但願所有移到中,則有三個步驟it
1)將1~N-1層塔先所有從左移動到右,交給遞歸過程io
2)將第N層塔從左移到中class
3)將1~N-1層塔所有從右移到中,明顯交給遞歸過程方法
若是把剩下的N層塔從中移到左,從中移到右,從右移到中過程與上述相同
若是剩下的N層都在左,但願所有移動到右。
1)將1~N-1層塔先所有從左移動到右,交給遞歸
2)將第N層塔從左移動到中
3)將1~N-1層塔從右移到左
4)N層從中移到右
5)最後將1~N-1層塔所有從左移到右,交給遞歸過程
若是剩下全在右,但願移到左,同上
public static int hanoiProblem1(int num, String left, String mid, String right) { if (num < 1) { return 0; } return process(num, left, mid, right, left, right); } public static int process(int num, String left, String mid, String right, String from, String to) { //只有一層須要移動的時候 if (num == 1) { //若是from或to有一個是mid,說明是從右往中,或從左往中,直接移動便可。 if (from.equals(mid) || to.equals(mid)) { System.out.println("Move 1 from " + from + " to " + to); return 1; } else { //不然說明是從左到右,或從右到左,都須要兩步,先到中間,再到目的地 System.out.println("Move 1 from " + from + " to " + mid); System.out.println("Move 1 from " + mid + " to " + to); return 2; } } //當層數大於1的時候,且有一個是到中間 if (from.equals(mid) || to.equals(mid)) { //若是是從左到中,或者中到左 another就爲右,不然就爲左,就是說如今參與的是左中,那麼剩下的一個是右,同理右中 String another = (from.equals(left) || to.equals(left)) ? right : left; //遞歸處理n-1層,從當前的from到另外一端,即從作到右,或從右到左 int part1 = process(num - 1, left, mid, right, from, another); //將第N層塔從左移到中或從右移到中 int part2 = 1; System.out.println("Move " + num + " from " + from + " to " + to); //將1~N-1層塔所有從右移到中,或者從左移動中 int part3 = process(num - 1, left, mid, right, another, to); return part1 + part2 + part3; } else { //從左到右或者從右到左 遞歸處理n-1層 //將1~N-1層塔先所有從左移動到右或從右移到左 int part1 = process(num - 1, left, mid, right, from, to); //將第N層塔從左移動到中 或從右移到中 int part2 = 1; System.out.println("Move " + num + " from " + from + " to " + mid); //將1~N-1層塔從右移到左 或相反 int part3 = process(num - 1, left, mid, right, to, from); //N層從中移到右 int part4 = 1; System.out.println("Move " + num + " from " + mid + " to " + to); //最後將1~N-1層塔所有從左移到右,交給遞歸過程 int part5 = process(num - 1, left, mid, right, from, to); return part1 + part2 + part3 + part4 + part5; } }
將左、中、右三個地點抽象成棧,LS、MS、RS,那麼棧的操做就能夠當作是:某一個棧(from)把棧頂元素彈出,而後壓入到另外一個棧裏(to),做爲另外一個棧(to)的棧頂。
遊戲的第一個動做必定是L->M,這是顯而易見的。
在走出最小部署過程當中的任什麼時候刻,四個動做只有一個動做不違反小壓大和相鄰不可逆原則,另外三個必定會違反
上述第二點證實:
假設前一步的動做是L->M:
- 根據小壓大的原則,L->M的動做不會重複發生
- 根據相鄰不可逆原則,M->L的動做也不應發生
- 根據小壓大的原則,M->R和R->M只會有一個達標
假設前一步的動做是M->L:
- 根據小壓大原則,M->L的動做不會重複發生
- 根據相鄰不可逆原則L->M也不會發生
- 根據小壓大原則,M->R和R->M只會有一個達標
假設前一步的動做是M->R:
- 根據小壓大的原則,M->R不會重複發生
- 根據相鄰不可逆原則,R->M也不會發生
- 根據小壓大的原則,L->M和M->L只會有一個達標
假設前一步的動做是R->M:
- 根據小壓大的原則,R->M的動做不會重複發生
- 根據相鄰不可你原則,M->R的動做也不應發生
- 根據小壓大原則,L->M和M->L只會 有一個達標
如上,每一步只會有一個動做達標,那麼只要每走一步都根據這兩個原則考察全部的動做就能夠,哪一個達標走哪一個。
public static enum Action { No, LToM, MToL, MToR, RToM } public static int hanoiProblem2(int num, String left, String mid, String right) { //左棧 Stack<Integer> lS = new Stack<Integer>(); //中棧 Stack<Integer> mS = new Stack<Integer>(); //右棧 Stack<Integer> rS = new Stack<Integer>(); lS.push(Integer.MAX_VALUE); mS.push(Integer.MAX_VALUE); rS.push(Integer.MAX_VALUE); //從num開始由大到小依次入左棧 for (int i = num; i > 0; i--) { lS.push(i); } Action[] record = { Action.No }; int step = 0; //若是右棧的個數不等於num+1說明尚未移動完 while (rS.size() != num + 1) { step += fStackTotStack(record, Action.MToL, Action.LToM, lS, mS, left, mid); step += fStackTotStack(record, Action.LToM, Action.MToL, mS, lS, mid, left); step += fStackTotStack(record, Action.RToM, Action.MToR, mS, rS, mid, right); step += fStackTotStack(record, Action.MToR, Action.RToM, rS, mS, right, mid); } return step; }