Cannibals & Missioneries
遊戲網址
野人與傳教士
遊戲的內容是這樣:幫助三個野人與三位傳教士過河,過河的工具為一艘船,這船一次最多只能搭載兩人,而且當其中一邊野人人數大於傳教士時,野人吃掉就會傳教士,讓這六個人過河失敗。
請問,要如何安全的讓這六個人輪流過河?
首先,要用暴力搜尋法找出所有解,一定要先做資料規劃:
1.全部搭船過河的搭配狀況為,傳教士一人、傳教士兩人,傳教與野人各一人,野人一人,野人兩人。還會有第六種嗎?沒了,總不能空船自己過河吧?
2.船一去一返,故全部完成過河時,總步驟數一定為單數。既然全部都過了河,船也就沒有必要把它弄回來,故為結束條件。
3.去程與返程搭船搭配狀況同條件1,故,過河步數可用單雙來表示去程或回程,並依此條件來增減本岸或對岸人數狀況。
4.全部到達對岸即搜尋結束,更換搜尋其他可符合的組合。
5.一定要記錄本岸(或對岸)人數狀況,同時也要排除送兩個野人過河後,又把兩個野人給送回來,一去一返後本岸(或對岸)人數完全相同之重複過河情況。
6.必要時增加步驟數最大上限,防止搜尋時出錯造成程式無法中斷。
實作:
' 需一個 Command , Name = Command1 ;一個 ListBox , Name = List1 。- Option Explicit
- ' 過河
- Private Const MaxStep = 100 ' 預設步數上限值
- Dim b(1, MaxStep) As Integer ' 本岸人數狀況
- ' b(0, x) = 本岸傳教 x 步時人數
- ' b(1, x) = 本岸野人 x 步時人數
- Dim r(MaxStep) As Integer ' 目前正在執行的過河動作 (路徑)
- Dim Peoples As Integer ' 人數上限
- Dim Counts As Integer ' 符合組數
- Private Sub Command1_Click()
- List1.Clear
- Peoples = 3 ' 設定過河人數
- Counts = 0 ' 清除解答數
- b(0, 0) = Peoples ' 設定本岸傳教人數
- b(1, 0) = Peoples ' 設定本岸傳教人數
- run 0 ' 開始搜尋
- MsgBox "過河搜尋完畢,解答數為:" & vbCrLf & Counts
- End Sub
- Sub run(iStep As Integer)
- ' 執行過河動作, 傳入步數
- Dim i As Integer, j As Integer
- If iStep > MaxStep Then Exit Sub ' 防溢位
- j = iStep + 1 ' 步驟數加一
- r(iStep) = 1 ' 設定傳教二人過河, 須對應觀察函式設定
- i = runChk(2, 0, iStep) ' 取得檢查變數, 供除錯用
- If i = 1 Then ' 未發生吃人, 繼續往下搜尋; 已有解就不呼叫遞迴
- ' Debugs iStep ' 觀察求解過程, 請自行添加觀察
- runOK 2, 0, iStep ' 處理下個過河動作
- run j
- End If
- r(iStep) = 2 ' 設定傳教一人過河
- If runChk(1, 0, iStep) = 1 Then ' 直接判斷, 簡化程式碼與升速
- runOK 1, 0, iStep
- run j
- End If
- r(iStep) = 3 ' 設定傳教與野人各一人過河
- If runChk(1, 1, iStep) = 1 Then
- runOK 1, 1, iStep
- run j
- End If
- r(iStep) = 4 ' 設定野人各一人過河
- If runChk(0, 1, iStep) = 1 Then
- runOK 0, 1, iStep
- run j
- End If
- r(iStep) = 5 ' 設定野人各二人過河
- If runChk(0, 2, iStep) = 1 Then
- runOK 0, 2, iStep
- run j
- End If
- End Sub
- Function runChk(s1 As Integer, s2 As Integer, st As Integer) As Integer
- ' 判斷過河還是返回, 執行成功傳回 1, 否則為 0
- ' 若已全部渡河完畢, 傳回 0, 並秀出解答
- ' s1 為傳教人數, s2 為野人人數, st 為執行步數
- Dim i As Integer
- runChk = 0
- If st > 0 Then ' 第二步以上要做重複路徑檢查
- If r(st) = 0 Then Exit Function ' 詭異路徑, 防 Bug
- If r(st - 1) = r(st) Then Exit Function ' 檢查是否跟上一動作一樣
- If st Mod 2 = 0 Then
- ' 去程處理
- If b(0, st) - s1 = 0 And b(1, st) - s2 = 0 Then ' 是否完成渡河
- chkEnd st ' 顯示解答處理
- Counts = Counts + 1 ' 解答數累加
- Exit Function
- End If
- For i = 0 To st - 1 Step 2 ' 防止重複路徑搜尋
- If b(0, i) = b(0, st) And b(1, i) = b(1, st) Then Exit Function
- Next
- End If
- End If
- If st Mod 2 = 0 Then
- ' 去程
- If b(0, st) - s1 < 0 Or b(1, st) - s2 < 0 Then Exit Function ' 過河人數是否合理
- If b(0, st) - s1 = 0 Or b(0, st) - s1 = Peoples Then ' 傳教都在同一岸, 絕對安全
- runChk = 1
- Exit Function
- End If
- If b(0, st) - s1 < b(1, st) - s2 Then Exit Function ' 本岸傳教太少
- If Peoples - b(0, st) + s1 < Peoples - b(1, st) + s2 Then Exit Function ' 對岸傳教太少
- Else
- ' 回程
- If b(0, st) + s1 > Peoples Or b(1, st) + s2 > Peoples Then Exit Function ' 返回人數是否合理
- If b(0, st) + s1 = Peoples And b(1, st) + s2 = Peoples Then Exit Function ' 不需要返回
- If b(0, st) + s1 = 0 Or b(0, st) + s1 = Peoples Then ' 傳教都在同一岸, 絕對安全
- runChk = 1
- Exit Function
- End If
- If b(0, st) + s1 < b(1, st) + s2 Then Exit Function ' 本岸傳教太少
- If Peoples - b(0, st) - s1 < Peoples - b(1, st) - s2 Then Exit Function ' 對岸傳教太少
- End If
- runChk = 1
- End Function
- Sub chkEnd(st As Integer)
- ' 顯示路徑搜尋結果, 傳入執行步數
- Dim i As Integer
- Dim s As String
- s = ""
- For i = 0 To st
- s = s & r(i)
- Next
- ' 若想把輸出改成檔案時, 請取消下三行註解
- 'Open "C:\Ap.txt" For Append As #1
- 'Print #1, s
- 'Close #1
- List1.AddItem s
- End Sub
- Sub runOK(s1 As Integer, s2 As Integer, st As Integer)
- ' 處理過河人數狀況, 分別代入過河傳教人數, 野人人數, 與步驟數
- ' 雙數為過河, 單數為返回
- If st Mod 2 = 0 Then
- b(0, st + 1) = b(0, st) - s1
- b(1, st + 1) = b(1, st) - s2
- Else
- b(0, st + 1) = b(0, st) + s1
- b(1, st + 1) = b(1, st) + s2
- End If
- End Sub
- Sub Debugs(iStep As Integer)
- ' 觀察求解過程用
- Dim i As Integer
- Dim s As String
- Debug.Print iStep, r(iStep),
- s = Left(String(b(0, iStep), "人") & " ", 3) & Left(String(b(1, iStep), "鬼") & " ", 3)
- If iStep Mod 2 = 0 Then
- s = s & " "
- Else
- s = s & " <- "
- End If
- Select Case r(iStep)
- Case 1
- s = s & "人人"
- Case 2
- s = s & "人 "
- Case 3
- s = s & "人鬼"
- Case 4
- s = s & " 鬼"
- Case 5
- s = s & "鬼鬼"
- End Select
- If iStep Mod 2 = 0 Then
- s = s & " -> "
- Else
- s = s & " "
- End If
- Debug.Print s & Left(String(Peoples - b(0, iStep), "人") & " ", Peoples) & Left(String(Peoples - b(1, iStep), "鬼") & " ", Peoples)
- End Sub
複製代碼 說明:
1.本程式使用遞迴求解,對於遞迴使用請多加研究。
2.解出來的路徑為最佳路徑解,自然是步驟數越少越好。
3.請勿直接拿本原始碼當作業交差了事,被教授逮著不給分是不關本文的事,請小心服用。
本文同步刊載於:http://tw.myblog.yahoo.com/shege-1975/article?mid=1988 |