漢羅塔問題是一個非常經典的算法,我們首先來研究一下修改的漢羅塔(簡化步驟),在後面我們將來講述經典的漢羅塔問題。
題目:
修改後的漢羅塔的規則:現在限制不能從最左側的塔直接移動到最右側,必需要經過中間;同時從最右側移動到最左測試,同樣必需經過中間;要求移動N層塔時,打印最優移動
1、用遞歸函數實現(從最左移動到最右)
分析:
- 當只有一層塔時,我們先需要將其從左移到中間,再從中間移動到右邊,共分爲兩步;如果它就在中間,那麼我們只需要將它移動到左或則右,一步就行;
- 當我們有N 層塔時,我們需要先將1~N-1層塔移動到右邊,然後移動第N層塔到中間,再將1~N-1層塔移動到最左邊,將N層塔由中間移動右邊;這樣,第N層塔就移好了
- 接下來重複上述步驟,將1~N-2層塔移到最右邊,將第N-1層塔移到最中間……(利用遞歸函數實現)
代碼實現:
void _HanoiProblem1(int num,string from,string to,int& count)//num代表塔的層數,from代表開始移動的位置(left,mid,right),to 代表要移動到的位置
{
if(num == 1)//我們只有一層塔
{
if(from.compare("mid")==0 || to.compare("mid")==0)//此時只需直接移動
{
count++;
cout<<"move "<<num<<" from "<<from.c_str()<<" to "<<to.c_str()<<endl;
}
else
{
cout<<"move "<<num<<" from "<<from.c_str()<<" to "<<"mid"<<endl;
count++;
cout<<"move "<<num<<" from "<<"mid"<<" to "<<to.c_str()<<endl;
count++;
}
}
else//我們有多層塔
{
if(from.compare("mid")==0 || to.compare("mid")==0)
{
string another;
if(from.compare("left")==0)
another = "right";
else
another = "left";
_HanoiProblem1(num-1,from,another,count);//例如從左移到中間,先將1~N-1層塔移動到右邊,
cout<<"move "<<num<<" from "<<from.c_str()<<" to "<<to.c_str()<<endl;//再將第N層塔移到中間
count++;
_HanoiProblem1(num-1,another,to,count);//再將1~N-1層塔移到中間
}
else//從左移到右或從右移到左
{
_HanoiProblem1(num-1,from,to,count);//例如從左移到右,先將1~N-1層塔從左移動到右邊,
cout<<"move "<<num<<" from "<<from.c_str()<<" to "<<"mid"<<endl;//再將第N層塔移到中間
count++;
_HanoiProblem1(num-1,to,from,count);//再將1~N-1層塔從右移動到左邊
cout<<"move "<<num<<" from "<<"mid"<<" to "<<to.c_str()<<endl;//再將第N層塔從中間移到右邊
count++;
_HanoiProblem1(num-1,from,to,count);//再將1~N-1層塔從左移動到右邊
}
}
}
void HanoiProblem1(int num,string from,string to)
{
int count = 0;
_HanoiProblem1(num,from,to,count);
cout<<"count is: "<<count<<endl;
}
測試代碼:
void funtest()
{
HanoiProblem1(2,"left","right");
}
int main()
{
funtest();
getchar();
return 0;
}
結果圖
2.用棧模擬實現
分析:
我們上面用遞歸實現,我們已經知道了基本的走法,接下來我們用棧來模擬漢羅塔問題,將塔的移動轉換爲入棧和出棧的操作,但是,由題我們知道了參數入棧和出棧的兩個基本規則
- 小壓大問題,即只有當要入棧的參數小於棧頂元素,這時我們才能入棧
- 動作不想臨,題目要求我們實現最優移動,所以我們從左移動到中間,下一步將它從中間右移動到左邊,是沒有意義的
滿足了以上兩條規則,我們現在看移動的過程,一個塔a,只有四中可能的動作,從左到中,從中到右,從右到中,從中到左,但是要滿足以上兩種規則我們發現它只有一種動作可以走;例如:a在最左邊,第一次,它只能從左到中間,第二次,由規則知,從左到中間不行,從中間到左沒有意義,那麼只剩下從中間到右和從右到中間,我們只需判斷就能得到結果。
代碼實現:
enum action
{
No,
LToM,
MToL,
RToM,
MToR,
};
int fStackToStack(action& record,action preAction,action nowAction,stack<int>& fstack,stack<int>& tstack,string from,string to)
{
if(record != preAction && fstack.top() < tstack.top())//滿足兩條準則,動作不可逆和小壓大原則
{
int data = fstack.top();
fstack.pop();
tstack.push(data);
cout<<"move "<<data<<" from "<<from.c_str()<<" to "<<to.c_str()<<endl;
record = nowAction;
return 1;
}
return 0;
}
int HanoiProblem2(int num,string left,string mid,string right)
{
stack<int> ls;
stack<int> ms;
stack<int> rs;
ls.push(INT_MAX);
ms.push(INT_MAX);
rs.push(INT_MAX);
for(int idx=num; idx>0; --idx)
ls.push(idx);
action record = No;//記錄上一步的動作
int count = 0;
while(rs.size() != num+1)
{
count += fStackToStack(record,MToL,LToM,ls,ms,left,mid);
count += fStackToStack(record,LToM,MToL,ms,ls,mid,left);
count += fStackToStack(record,MToR,RToM,rs,ms,right,mid);
count += fStackToStack(record,RToM,MToR,ms,rs,mid,right);
}
return count;
}
測試代碼:
void funtest()
{
int ret = HanoiProblem2(2,"left","mid","right");
cout<<ret<<endl;
}
int main()
{
funtest();
getchar();
return 0;
}
結果圖: