有三個和尚(或傳教士)和三個妖怪(或食人怪)過河,只有一條能裝下兩個人(和尚或妖怪)的 船,在河的任何一方或者船上,如果妖怪的人數大於和尚的人數,那麼和尚就會有被吃掉的危險。你 能不能找出一種安全的渡河方法呢?
這是一個很有意思的智力題,但是並不難,每次可以選擇 一個人或者兩個人過河,只要保證在河的任何一邊的和尚數量總是大於或等於妖怪的數量即可。這裡 先給出一種過河方法:
兩個妖怪先過河,一個妖怪回來;
再兩個妖怪過河,一個妖怪 回來;
兩個和尚過河,一個妖怪和一個和尚回來;
兩個和尚過河,一個妖怪回來;
兩個妖怪過河,一個妖怪回來;
兩個妖怪過河。
過河的方法其實不止這一種, 本文給出了一種求解全部過河方法的演算法程式,可以通過窮舉(狀態樹搜尋)的方法得到全部四種過 河方法。
解決問題的思路
題目的初始條件是三個和尚和三個妖怪在河的一邊(還有一 條船),解決問題後的終止條件是三個和尚和三個妖怪安全地過到河的對岸,如果把任意時刻妖怪和 和尚的位置看作一個“狀態”,則解決問題就是找到一條從初始狀態變換到終止狀態的路徑。從初始 狀態開始,每選擇一批妖怪或和尚過河(移動一次小船),就會從原狀態產生一個新的狀態,如果以 人類思維解決這個問題,每次都會選擇最佳的妖怪與和尚組合過河,使得它們過河後產生的新狀態更 接近最終狀態,不斷重複上述過程,直到得到最終狀態。
用電腦解決妖怪與和尚過河問題的 思路也是通過狀態轉換,找到一條從初始狀態到結束狀態的轉換路徑。電腦不會進行理性分析,不 知道每次如何選擇最佳的過河方式,但是電腦擅長快速計算且不知疲勞,既然不知道如何選擇過河 方式,那就乾脆把所有的過河方式都嘗試一遍,找出所有可能的結果,當然也就包括成功過河的結果 。
這個思路其實和《三隻水桶等分水問題》的解法類似,從初始狀態開始,通過構造特定的窮 舉演算法,對解空間中的所有狀態進行窮舉,就得到一棵以初始狀態為根的狀態樹。如果狀態樹上某個 葉子節點是題目要求的最終狀態,則從根節點到此葉子節點之間的所有狀態節點就是一個過河問題的 解決過程。
狀態的數學模型
本節探討一下如何建立狀態的數學模型。題目要求並不強調三個妖怪之間或三個和尚之間的差異, 只是關注它們在和河兩岸的數量,因此無需賦予和尚和妖怪過多的屬性,只要用數值分別表示它們在 和兩岸的數量即可確定某個時刻的狀態。除了和尚與妖怪的數量,還有一個很關鍵的因素也會影響到 數學模型,那就是船的狀態。例如某一時刻,本地河邊有兩個和尚和兩個妖怪,對岸有一個和尚和一 個妖怪,此時船在河這邊和在河對岸就分別是兩個完全不同的狀態。和尚與妖怪的狀態就是數值,船 有兩個狀態,在本地河邊(LOCAL)和在對岸(REMOTE),我們用一個五元組來表示某個時刻的過河狀 態:[本地和尚數,本地妖怪數, 對岸和尚數,對岸妖怪數,船的位置]。用五元組表示的初始狀態就 是[3, 3, 0, 0, LOCAL],問題解決的過河狀態是[0, 0, 3, 3, REMOTE]。用C/C++定義此狀態模型如 下:
28 struct ItemState 29 { 30 ...... 31 int local_monster; 32 int local_monk; 33 int remote_monster; 34 int remote_monk; 35 BoatLocation boat; /*LOCAL or REMOTE*/ 36 ...... 37 };
本題的狀態空間就是以[3, 3, 0, 0, LOCAL]為根的一棵狀態樹,如果某個葉子節點表 示的狀態是求解狀態[0, 0, 3, 3, REMOTE],則從根節點到此節點之間的直系關聯節點,就是過河過 程中的所有中間狀態,將這些中間狀態按照父子關係依次輸出,就是一個求解過程。以本文開始給出 的一個求解過程為例,其狀態轉換過程如下圖所示:
圖(1)一個求解的狀態轉換過程