漢諾塔(Hanoi)算法,應該是每一個程序員都會學習到的遞推算法之一,漢諾塔是一個很著名的智力題,但是這裏就不科普它的由來了,我們直接進入正題。
如上圖,假設A棒有五個原盤,依次移動,每次移動一塊,小的永遠只能在上面,最終移動到C棒上,如何用算法實現呢?
從這裏移動的邏輯我們很容易發現,A幫不就像一個棧嗎,棧頂必須先出,網上看過很多漢諾塔算法,很少涉及到用棧實現,的確,算法拿出來了,用什麼都一樣,在我學習的時候,教材上是用的char,直接模擬推算,沒用真正移動數據實現真正的Hanoi思想,所以,琢磨了一會,寫了一個用棧實現的算法。
首先,既然是棧,爲了方便跟蹤,寫了一個自己的MyStack包裝了一下Java的Stack,貼上代碼:
class MyStack{
private String name;
private Stack<Integer> data;
public MyStack(String name){
this.name=name;
data=new Stack<>();
}
public String getName(){
return this.name;
}
public void push(int data){
if(!this.data.isEmpty()&&this.data.peek()<data){
System.out.println("出錯");
}
this.data.push(data);
}
public int peek(){
return data.peek();
}
public int pop(){
return data.pop();
}
public int size(){
return data.size();
}
public boolean isEmpty(){
return data.isEmpty();
}
}
然後就是Hanoi遞推的實現,還是貼上圖片
我們要按照Hanoi的邏輯將原盤從A棒移動到C棒,那麼,就必須以B棒作爲媒介,
最大的必須在最下面,所以,我們必須把上面4個圓盤先移動到B棒上。
但是又看,上面4個,要想把第四個移動到B棒,就得用C棒暫時當媒介,先把上面3個移動到C棒,以此類推,知道只有最後一個的時候,就可以直接移動了,所以,我們要做的就是想出一個算法,遞推到只剩一個圓盤,然後慢慢回棧,到第二,第三,第四,最後第五。
先貼上代碼:
public static void hanoi(int size,MyStack a,MyStack b,MyStack c){
if(size==1){
c.push(a.pop());
}else{
int n=b.size();
hanoi(size-1,a,c,b);
c.push(a.pop());
hanoi(b.size()-n,b,a,c);
}
}
首先解釋參數中的size,因爲棧無法在不取出元素的情況下遞減長度,所以增加了參數size作爲棧的圓盤指針,限定只能移動size個圓盤。
所以,當size==1的時候,就是只剩下一個圓盤,那麼就順理成章的直接移動到C棒了,不必在意此時的C棒回棧後是B棒還是A棒,那不是此時遞歸該擔心的事情。
在算法中,如果size!=1,那麼說明我們需要一個作爲媒介,讓size-1個圓盤先暫時放到媒介上,然後將第size個圓盤放過去,所以,我們需要進行一次遞歸,將第size-1個上面的圓盤重新進行計算該存放的位置,計算完成後,然後放入第size個圓盤到C幫,然後再將媒介B棒中的圓盤又以A棒爲媒介,以此方式放入C盤。
至於爲什麼要在遞歸前緩存一次B棒的size,因爲進入遞歸前不知道B棒是否有數據,說不定此次計算正是上一次的遞歸呢,不知道後面的方法B棒會是怎樣的存在,不知道會進入多少次遞歸,假設B棒在進入第一次遞歸前長度爲2,遞歸完後,長度爲5,第二次遞歸時,如果不限制size長度,直接使用B棒的size,那麼,除了第一次遞歸時增加的3個數據,還會把原本的2個數據一起計算進去,博主就在這個坑繞了一些時間,最後跟蹤了一下才明白這個。
文字有點多,最後貼上完整代碼:
public class Hanoi {
private static int m=1;
private static MyStack a=new MyStack("A");
private static MyStack b=new MyStack("B");
private static MyStack c=new MyStack("C");
public static void main(String[] args) {
for(int i=5;i>0;i--){
a.push(i);
}
hanoi(a.size(),a,b,c);
print(c);
}
public static void hanoi(int size,MyStack a,MyStack b,MyStack c){
if(size==1){
System.out.println("第"+m+++"步,從"+a.getName()+"移動了 "+a.peek()+"到了"+c.getName());
c.push(a.pop());
}else{
int n=b.size();
hanoi(size-1,a,c,b);
System.out.println("第"+m+++"步,從"+a.getName()+"移動了 "+a.peek()+"到了"+c.getName());
c.push(a.pop());
hanoi(b.size()-n,b,a,c);
}
}
public static void print(MyStack temp){
System.out.println("size:"+temp.size());
for(int i=0,n=temp.size();i<n;i++){
System.out.print(temp.pop()+" ");
}
System.out.println();
}
}
class MyStack{
private String name;
private Stack<Integer> data;
public MyStack(String name){
this.name=name;
data=new Stack<>();
}
public String getName(){
return this.name;
}
public void push(int data){
if(!this.data.isEmpty()&&this.data.peek()<data){
System.out.println("出錯");
}
this.data.push(data);
}
public int peek(){
return data.peek();
}
public int pop(){
return data.pop();
}
public int size(){
return data.size();
}
public boolean isEmpty(){
return data.isEmpty();
}
}
核心算法只有那一段,其他是我爲了方便跟蹤增加的,各位在測試的時候可以選擇性刪除。