深搜+小孩分遊問題
1.問題描述
小孩分油問題
兩個小孩去打油,一人帶了一個一斤的空瓶,另一個帶了一個七兩、一個三兩的空瓶。原計劃各打一斤油,可是由於所帶的錢不夠,只好兩人合打了一斤油,在回家的路上,兩人想平分這一斤油,可是又沒有其它工具。試僅用三個瓶子(一斤、七兩、三兩)精確地分出兩個半斤油來。
2.算法設計
令狀態R、E、S分別表示十兩、七兩和三兩的瓶子中所裝的油量。如問題所述,初始時有(R,E,S)=(10,0,0),問題要求即僅通過這三個瓶子,將油量狀態轉變為(R,E,S)=(5,5,0)。
該問題較為特殊,我們發現七兩的瓶子和三兩的瓶子所能裝的油的總量恰好為十兩。因此,我們可以將十兩的瓶子等同於一個無窮大的油桶,任何時候七兩和三兩的瓶子都可以通過這個油桶裝滿或倒空油。在這個假設下,原問題即被簡化為:初始狀態(E、S)=(0,0),要求僅通過這兩個瓶子,將狀態轉變為(E、S)=(5,0)。在這個目標狀態下,十兩的瓶子中自然裝了五兩油。
將兩個瓶子的狀態轉變以及對應的規則列表如下:
規則號 |
規則 |
解釋 |
1 |
(E,S) and E<7 → (7,S) |
7兩瓶不滿時裝滿 |
2 |
(E,S) and S<3 → (E,3) |
3兩瓶不滿時裝滿 |
3 |
(E,S) and E>0 → (0,S) |
7兩瓶不空時倒空 |
4 |
(E,S) and S>0 → (E,0) |
3兩瓶不空時倒空 |
5 |
(E,S) and E>0 and E+S≤3 → (0,E+S) |
37兩瓶中油全倒入3兩瓶 |
6 |
(E,S) and S>0 and E+S≤7 → (E+S,0) |
3兩瓶中油全倒入7兩瓶 |
7 |
(E,S) and S<3 and E+S≥3 → (E+S-3,3) |
用7兩瓶油裝滿3兩瓶子 |
8 |
(E,S) and E<7 and E+S≥7 → (7,E+S-7) |
用3兩瓶油裝滿7兩瓶子 |
在每個狀態(E,S)下,我們均可以通過判斷E、S的值來選擇上述若幹條規則進行狀態轉變。整個狀態空間構成了一顆樹,樹根是初始狀態(R,S)=(0,0),目標狀態(R,S)=(5,0)則可能位於某些節點中。
因為該問題的狀態空間較小,最多不超過(7*3=21)種狀態,因此在實驗中我們采用深度搜索的方法對問題進行求解。此外,為了避免對已經搜索過的狀態重復搜索,程序中定義了一個數組,用於存儲已經搜索過的狀態,僅有當當前狀態沒有在該數組中出現過時,算法才對其進行搜索,並將該狀態放入數組中。
3.程序流程
4.核心偽代碼
function isVisited(E, S): 狀態(E, S)是否搜索過,沒有則將其入棧並標記已搜索。
初始狀態(E, S) = (0, 0),並存入棧Stack
while 棧Stack不為空:
取出棧頂元素(E, S),並輸出
If (E, S) == (5, 0), then
分油成功,break;
if E < 7, then:
(E, S) = (7, S), isVisited(E, S)
if E < 3, then:
(E, S) = (E, 3), isVisited(E, S)
if E > 0, then:
(E, S) = (0, S), isVisited(E, S)
if S > 0, then:
(E, S) = (E, 0), isVisited(E, S)
if E > 0 and E+S <= 3, then:
(E, S) = (0, E+S), isVisited(E, S)
if S > 0 and E+S <= 7, then:
(E, S) = (E+S, 0), isVisited(E, S)
if S < 3 and E+S >= 3, then:
(E, S) = (7, S), isVisited(E+S-3, 3)
if E < 7 and E+S >= 7, then:
(E, S) = (7, S), isVisited(7, E+S-7)
end
5.代碼運行及測試
算法運行結果如下所示,經過10次操作後,準確得將油劃分為兩個五兩。
6.結論
本實驗是對狀態空間采用深搜的方法實現的。程序中設置了輔助數組用於保存已經搜索過的狀態,且該問題的狀態空間很小,因此深搜不會出現無窮解的情況。只要目標狀態設置合理且存在,深搜一定能在有限的步驟裏求得。但是在小孩分油問題中,深搜所得結果不一定為最優,廣搜下得到的結果才是最優結果。但是由於深搜易於實現且速度快,因此實驗中才選擇深搜去實現。
本實驗源碼具有較強的擴展性,只要初始狀態和目標狀態設置合理,程序均可以成功將其狀態轉換過程輸出。
7.源碼
#include<iostream> #include<stack> #include<vector> using namespace std; struct State { int E; // 七兩的瓶子 int S; // 三兩的瓶子 State(int E, int S) { this->E = E; this->S = S; } }; // 深搜輔助棧 stack<State> Stack; // 存儲已經出現過的狀態 vector<State> visited; // 查詢狀態s先前是否出現過 bool isVisited(State s) { vector<State>::iterator it; for (it = visited.begin(); it != visited.end(); it++) { if (it->E == s.E && it->S == s.S) return true; } return false; } // 倒油行為,狀態轉變 void move(State s) { // 查詢當前狀態先前是否訪問過 if (!isVisited(s)) { visited.push_back(s); Stack.push(s); } } int main() { int E = 0, S = 0; int fE = 5, fS = 0; cout<<"Please input the initial oil of bottles:"<<endl; cin>>E>>S; cout<<"Please input the final oil of bottles:"<<endl; cin>>fE>>fS; Stack.push(State(E, S)); while(!Stack.empty()) { State cur = Stack.top(); Stack.pop(); E = cur.E; S = cur.S; cout<<10 - E - S<<" "<<E<<" "<<S<<endl; // 到達目標狀態 if (E == fE && S == fS) { cout<<"Successfully reach the target state:("<<fE<<", "<<fS<<")!"; return 0; } // 將七兩的瓶子裝滿 if (E < 7) move(State(7, S)); // 將三兩的瓶子裝滿 if (S < 3) move(State(E, 3)); // 將七兩的瓶子倒空 if (E > 0) move(State(0, S)); // 將三兩的瓶子倒空 if (S > 0) move(State(E, 0)); // 將七兩的瓶子全部裝到三兩的瓶子上 if (E > 0 && E + S <= 3) move(State(0, E + S)); // 將三兩的瓶子全部裝到七兩的瓶子上 if (S > 0 && E + S <= 7) move(State(E + S, 0)); // 用七兩的瓶子將三兩的瓶子裝滿 if (S < 3 && E + S >= 3) move(State(E + S - 3, 3)); // 用三兩的瓶子將七兩的瓶子裝滿 if (E < 7 && E + S >= 7) move(State(7, E + S - 7)); } cout<<"Algorithm cannot find a solution!"<<endl; return 0; }
深搜+小孩分遊問題