題目:
漢諾塔問題比較經典,這裏修改一下遊戲規則:
現在限制不能從最左側的塔直接移動到最右 側,也不能從最右側直接移動到最左側,而是必須經過中間。求當塔有 N 層的時候,打印
最優移動過程和最優移動總步數。
例如,當塔數爲兩層時,最上層的塔記爲 1,最下層的塔記爲 2,則打印:
Move 1 from left to mid
Move 1 from mid to right
Move 2 from left to mid
Move 1 from right to mid
Move 1 from mid to left
Move 2 from mid to right
Move 1 from left to mid
Move 1 from mid to right
It will move 8 steps.
【要求】
用以下兩種方法解決。
方法一:遞歸的方法;
方法二:非遞歸的方法,用棧來模擬漢諾塔的三個塔。
這個問題和經典的漢諾塔很像咯,漢諾塔是最經典的聯繫遞歸的算法了,這裏左神用遞歸和非遞歸(棧)兩種方式來實現了;
遞歸
// 遞歸做 public static int hanoiProblem1(int N,String left,String mid,String right){ if(N<1) return 0; return process1(N,left,mid,right,left,right); } // from —> to public static int process1(int N,String left,String mid,String right,String from,String to){ // 只有1層塔的情況 if(N==1){ // from/to存在mid的情況,直接一步就可完成from,to(遊戲規則,只可以藉助mid進行移動) if(from.equals(mid) || to.equals(mid)){ System.out.println("move 1 from "+from+" to "+to); return 1; }else{ // 移動不是在mid上進行,則需要藉助mid進行兩步操作 System.out.println("move 1 from "+from+" to mid"); System.out.println("move 1 from mid to "+to); return 2; } } // 其他情況(即 多層塔層疊) // 存在mid移動的情況,分三步 if(from.equals(mid) || to.equals(mid)){ // 3 steps // 現在要把塔從from->to上,因爲是多層塔,所以需要藉助另外的塔把壓在上面的塔移走,這樣最下面的塔才能移到to上啊~~~ String another=from.equals(left) || to.equals(left)?right:left; int part1=process1(N-1,left,mid,right,from,another); System.out.println("move "+N+" from "+from+" to "+to); int part2=process1(N-1,left,mid,right,another,to); return part1+part2+1; }else{ // 不是mid的情況,則需要藉助mid進行5步操作了 // 5 steps int part1=process1(N-1,left,mid,right,from,to); System.out.println("move "+N+" from "+from+" to mid"); int part2=process1(N-1,left,mid,right,to,from); System.out.println("move "+N+" from mid to "+to); int part3=process1(N-1,left,mid,right,from,to); return part1+part2+part3+2; } }
上面就是遞歸操作的算法了,主要是分情況進行分析,這裏要注意,很多人會問規則不是 只能從 mid 經過才能走嗎,爲什麼還有類似於
int part1=process1(N-1,left,mid,right,from,another);
這樣(可能直接 left->right的代碼呢),請注意,這是遞歸操作,我們只需要注意print的操作是完全符合 遊戲規則就可以了,遞歸操作進入再分析的時候就會符合遊戲規則啦~~
- 非遞歸操作(棧操作)
對於棧操作的時候,需要有幾點說明:
首先,對於移動漢諾塔,符合要求的操作只有4種,LToM,MToL,MToR,RToM;
其次,由於要求的是最少步數,也就是如果前一步是LToM,則這步絕對不可能是MToL(這樣不就循環操作了嗎,怎麼可能是最優步數);
最後,操作的規則,只能小壓大;
對了,還有一個小的trick,爲了使得初始化時都可以壓入數據,事先在棧中壓入最大值(不壓入的話,需要處理當棧爲空的情況);
鑑於上述規則,我們可以定義3個棧來進行操作,代碼如下:
public static enum Action {
No, LToM, MToL, MToR, RToM
}
// 棧做
public static int hanoiProblem2(int N,String left,String mid,String right){
if(N<1) return 0;
Stack<Integer> fStack=new Stack<Integer>();
Stack<Integer> mStack=new Stack<Integer>();
Stack<Integer> tStack=new Stack<Integer>();
fStack.push(Integer.MAX_VALUE);
mStack.push(Integer.MAX_VALUE);
tStack.push(Integer.MAX_VALUE);
for(int i=N;i>=1;i--){
fStack.push(i);
}
// record主要是記錄上一次的操作
Action[] record={Action.No};
int step=0;
while(tStack.size()!=N+1){
step+=process2(record,Action.MToL,Action.LToM,fStack,mStack,left,mid);
step+=process2(record,Action.LToM,Action.MToL,mStack,fStack,mid,left);
step+=process2(record,Action.RToM,Action.MToR,mStack,tStack,mid,right);
step+=process2(record,Action.MToR,Action.RToM,tStack,mStack,right,mid);
}
return step;
}
public static int process2(Action[] record,Action preNoAct,Action curAct,Stack<Integer> fStack,
Stack<Integer> tStack,String from,String to){
// 當上一次的操作 爲當前上次不允許的操作時,返回
// 當大壓小時,不符合操作規則,返回
if(record[0]==preNoAct || fStack.peek()>=tStack.peek()){
return 0;
}
// 本次操作允許,記錄
tStack.push(fStack.pop());
System.out.println("move "+tStack.peek()+" from "+from+" to "+to);
record[0]=curAct;
return 1;
}
以上就是這個問題的求解過程了,比較能練手,好評~~~