返回列表 回復 發帖

過河問題(一、二)

Cannibals & Missioneries

遊戲網址

野人與傳教士

遊戲的內容是這樣:幫助三個野人與三位傳教士過河,過河的工具為一艘船,這船一次最多只能搭載兩人,而且當其中一邊野人人數大於傳教士時,野人吃掉就會傳教士,讓這六個人過河失敗。

請問,要如何安全的讓這六個人輪流過河?

首先,要用暴力搜尋法找出所有解,一定要先做資料規劃:

1.全部搭船過河的搭配狀況為,傳教士一人、傳教士兩人,傳教與野人各一人,野人一人,野人兩人。還會有第六種嗎?沒了,總不能空船自己過河吧?
2.船一去一返,故全部完成過河時,總步驟數一定為單數。既然全部都過了河,船也就沒有必要把它弄回來,故為結束條件。
3.去程與返程搭船搭配狀況同條件1,故,過河步數可用單雙來表示去程或回程,並依此條件來增減本岸或對岸人數狀況。
4.全部到達對岸即搜尋結束,更換搜尋其他可符合的組合。
5.一定要記錄本岸(或對岸)人數狀況,同時也要排除送兩個野人過河後,又把兩個野人給送回來,一去一返後本岸(或對岸)人數完全相同之重複過河情況。
6.必要時增加步驟數最大上限,防止搜尋時出錯造成程式無法中斷。


實作:
' 需一個 Command , Name = Command1 ;一個 ListBox , Name = List1 。
  1. Option Explicit
  2. ' 過河
  3. Private Const MaxStep = 100     ' 預設步數上限值

  4. Dim b(1, MaxStep) As Integer    ' 本岸人數狀況
  5. ' b(0, x) = 本岸傳教 x 步時人數
  6. ' b(1, x) = 本岸野人 x 步時人數
  7. Dim r(MaxStep) As Integer   ' 目前正在執行的過河動作 (路徑)
  8. Dim Peoples As Integer      ' 人數上限
  9. Dim Counts As Integer       ' 符合組數

  10. Private Sub Command1_Click()
  11. List1.Clear
  12. Peoples = 3         ' 設定過河人數
  13. Counts = 0          ' 清除解答數
  14. b(0, 0) = Peoples   ' 設定本岸傳教人數
  15. b(1, 0) = Peoples   ' 設定本岸傳教人數
  16. run 0               ' 開始搜尋
  17. MsgBox "過河搜尋完畢,解答數為:" & vbCrLf & Counts
  18. End Sub

  19. Sub run(iStep As Integer)
  20. ' 執行過河動作, 傳入步數
  21. Dim i As Integer, j As Integer

  22. If iStep > MaxStep Then Exit Sub        ' 防溢位

  23. j = iStep + 1   ' 步驟數加一

  24. r(iStep) = 1    ' 設定傳教二人過河, 須對應觀察函式設定
  25. i = runChk(2, 0, iStep) ' 取得檢查變數, 供除錯用
  26. If i = 1 Then ' 未發生吃人, 繼續往下搜尋; 已有解就不呼叫遞迴
  27. '    Debugs iStep    ' 觀察求解過程, 請自行添加觀察
  28.     runOK 2, 0, iStep   ' 處理下個過河動作
  29.     run j
  30. End If

  31. r(iStep) = 2    ' 設定傳教一人過河
  32. If runChk(1, 0, iStep) = 1 Then     ' 直接判斷, 簡化程式碼與升速
  33.     runOK 1, 0, iStep
  34.     run j
  35. End If

  36. r(iStep) = 3    ' 設定傳教與野人各一人過河
  37. If runChk(1, 1, iStep) = 1 Then
  38.     runOK 1, 1, iStep
  39.     run j
  40. End If

  41. r(iStep) = 4    ' 設定野人各一人過河
  42. If runChk(0, 1, iStep) = 1 Then
  43.     runOK 0, 1, iStep
  44.     run j
  45. End If

  46. r(iStep) = 5    ' 設定野人各二人過河
  47. If runChk(0, 2, iStep) = 1 Then
  48.     runOK 0, 2, iStep
  49.     run j
  50. End If
  51. End Sub

  52. Function runChk(s1 As Integer, s2 As Integer, st As Integer) As Integer
  53. ' 判斷過河還是返回, 執行成功傳回 1, 否則為 0
  54. ' 若已全部渡河完畢, 傳回 0, 並秀出解答
  55. ' s1 為傳教人數, s2 為野人人數, st 為執行步數
  56. Dim i As Integer
  57. runChk = 0

  58. If st > 0 Then  ' 第二步以上要做重複路徑檢查
  59.     If r(st) = 0 Then Exit Function ' 詭異路徑, 防 Bug

  60.     If r(st - 1) = r(st) Then Exit Function ' 檢查是否跟上一動作一樣

  61.     If st Mod 2 = 0 Then
  62.         ' 去程處理
  63.         If b(0, st) - s1 = 0 And b(1, st) - s2 = 0 Then ' 是否完成渡河
  64.             chkEnd st           ' 顯示解答處理
  65.             Counts = Counts + 1 ' 解答數累加
  66.             Exit Function
  67.         End If
  68.         For i = 0 To st - 1 Step 2  ' 防止重複路徑搜尋
  69.             If b(0, i) = b(0, st) And b(1, i) = b(1, st) Then Exit Function
  70.         Next
  71.     End If
  72. End If

  73. If st Mod 2 = 0 Then
  74.     ' 去程
  75.     If b(0, st) - s1 < 0 Or b(1, st) - s2 < 0 Then Exit Function    ' 過河人數是否合理
  76.     If b(0, st) - s1 = 0 Or b(0, st) - s1 = Peoples Then            ' 傳教都在同一岸, 絕對安全
  77.         runChk = 1
  78.         Exit Function
  79.     End If
  80.     If b(0, st) - s1 < b(1, st) - s2 Then Exit Function                         ' 本岸傳教太少
  81.     If Peoples - b(0, st) + s1 < Peoples - b(1, st) + s2 Then Exit Function     ' 對岸傳教太少
  82. Else
  83.     ' 回程
  84.     If b(0, st) + s1 > Peoples Or b(1, st) + s2 > Peoples Then Exit Function    ' 返回人數是否合理
  85.     If b(0, st) + s1 = Peoples And b(1, st) + s2 = Peoples Then Exit Function   ' 不需要返回
  86.     If b(0, st) + s1 = 0 Or b(0, st) + s1 = Peoples Then            ' 傳教都在同一岸, 絕對安全
  87.         runChk = 1
  88.         Exit Function
  89.     End If
  90.     If b(0, st) + s1 < b(1, st) + s2 Then Exit Function                         ' 本岸傳教太少
  91.     If Peoples - b(0, st) - s1 < Peoples - b(1, st) - s2 Then Exit Function     ' 對岸傳教太少
  92. End If
  93. runChk = 1
  94. End Function

  95. Sub chkEnd(st As Integer)
  96. ' 顯示路徑搜尋結果, 傳入執行步數
  97. Dim i As Integer
  98. Dim s As String
  99. s = ""
  100. For i = 0 To st
  101.     s = s & r(i)
  102. Next
  103. ' 若想把輸出改成檔案時, 請取消下三行註解
  104. 'Open "C:\Ap.txt" For Append As #1
  105. 'Print #1, s
  106. 'Close #1
  107. List1.AddItem s
  108. End Sub

  109. Sub runOK(s1 As Integer, s2 As Integer, st As Integer)
  110. ' 處理過河人數狀況, 分別代入過河傳教人數, 野人人數, 與步驟數
  111. ' 雙數為過河, 單數為返回
  112. If st Mod 2 = 0 Then
  113.     b(0, st + 1) = b(0, st) - s1
  114.     b(1, st + 1) = b(1, st) - s2
  115. Else
  116.     b(0, st + 1) = b(0, st) + s1
  117.     b(1, st + 1) = b(1, st) + s2
  118. End If
  119. End Sub

  120. Sub Debugs(iStep As Integer)
  121. ' 觀察求解過程用
  122. Dim i As Integer
  123. Dim s As String
  124. Debug.Print iStep, r(iStep),
  125. s = Left(String(b(0, iStep), "人") & "   ", 3) & Left(String(b(1, iStep), "鬼") & "   ", 3)

  126. If iStep Mod 2 = 0 Then
  127.     s = s & "    "
  128. Else
  129.     s = s & " <- "
  130. End If

  131. Select Case r(iStep)
  132. Case 1
  133.     s = s & "人人"
  134. Case 2
  135.     s = s & "人 "
  136. Case 3
  137.     s = s & "人鬼"
  138. Case 4
  139.     s = s & " 鬼"
  140. Case 5
  141.     s = s & "鬼鬼"
  142. End Select

  143. If iStep Mod 2 = 0 Then
  144.     s = s & " -> "
  145. Else
  146.     s = s & "    "
  147. End If

  148. Debug.Print s & Left(String(Peoples - b(0, iStep), "人") & "   ", Peoples) & Left(String(Peoples - b(1, iStep), "鬼") & "   ", Peoples)
  149. End Sub
複製代碼
說明:

1.本程式使用遞迴求解,對於遞迴使用請多加研究。
2.解出來的路徑為最佳路徑解,自然是步驟數越少越好。
3.請勿直接拿本原始碼當作業交差了事,被教授逮著不給分是不關本文的事,請小心服用。

本文同步刊載於:http://tw.myblog.yahoo.com/shege-1975/article?mid=1988

過河問題(二)

這回,過河問題的條件升級了,原過河的吃人條件依然不變,人數各增加為五人,而船的載客量也變大了,最多能搭載三人,問,其符合解為?

同樣也需要做資料規劃,需要變更的規劃為:

1.除了原過河搭配狀況之外,還得增加傳教三人、傳教二人與野人一人、野人三人等合理狀況,請記得,傳教一人與野人二人是不能一塊搭船的,還沒過完河,人早就被吃了,故此條件不加入搜尋,不必浪費時間。

其餘的,請在除錯用函式裡新增這三種顯示結果。

實作,只列出修改的部分,其餘者請參閱過河問題(一):
  1. Private Sub Command1_Click()
  2. List1.Clear
  3. Peoples = 5         ' 設定過河人數
  4. Counts = 0          ' 清除解答數
  5. b(0, 0) = Peoples   ' 設定本岸傳教人數
  6. b(1, 0) = Peoples   ' 設定本岸傳教人數
  7. run 0               ' 開始搜尋
  8. MsgBox "過河搜尋完畢,解答數為:" & vbCrLf & Counts
  9. End Sub

  10. Sub run(iStep As Integer)
  11. ' 執行過河動作, 傳入步數
  12. Dim i As Integer, j As Integer

  13. If iStep > MaxStep Then Exit Sub        ' 防溢位

  14. j = iStep + 1   ' 步驟數加一

  15. r(iStep) = 1    ' 設定傳教二人過河, 須對應觀察函式設定
  16. i = runChk(2, 0, iStep) ' 取得檢查變數, 供除錯用
  17. If i = 1 Then ' 未發生吃人, 繼續往下搜尋; 已有解就不呼叫遞迴
  18. '    Debugs iStep    ' 觀察求解過程, 請自行添加觀察
  19.     runOK 2, 0, iStep   ' 處理下個過河動作
  20.     run j
  21. End If

  22. r(iStep) = 2    ' 設定傳教一人過河
  23. If runChk(1, 0, iStep) = 1 Then     ' 直接判斷, 簡化程式碼與升速
  24.     runOK 1, 0, iStep
  25.     run j
  26. End If

  27. r(iStep) = 3    ' 設定傳教與野人各一人過河
  28. If runChk(1, 1, iStep) = 1 Then
  29.     runOK 1, 1, iStep
  30.     run j
  31. End If

  32. r(iStep) = 4    ' 設定野人各一人過河
  33. If runChk(0, 1, iStep) = 1 Then
  34.     runOK 0, 1, iStep
  35.     run j
  36. End If

  37. r(iStep) = 5    ' 設定野人二人過河
  38. If runChk(0, 2, iStep) = 1 Then
  39.     runOK 0, 2, iStep
  40.     run j
  41. End If

  42. r(iStep) = 6    ' 設定傳教三人過河
  43. If runChk(3, 0, iStep) = 1 Then
  44.     runOK 3, 0, iStep
  45.     run j
  46. End If

  47. r(iStep) = 7    ' 設定傳教二人, 野人一人過河
  48. If runChk(2, 1, iStep) = 1 Then
  49.     runOK 2, 1, iStep
  50.     run j
  51. End If

  52. r(iStep) = 8    ' 設定野人三人過河
  53. If runChk(0, 3, iStep) = 1 Then
  54.     runOK 0, 3, iStep
  55.     run j
  56. End If
  57. End Sub

  58. Sub Debugs(iStep As Integer)
  59. ' 觀察求解過程用
  60. Dim i As Integer
  61. Dim s As String
  62. Debug.Print iStep, r(iStep),
  63. s = Left(String(b(0, iStep), "人") & "   ", 3) & Left(String(b(1, iStep), "鬼") & "   ", 3)

  64. If iStep Mod 2 = 0 Then
  65.     s = s & "    "
  66. Else
  67.     s = s & " <- "
  68. End If

  69. Select Case r(iStep)
  70. Case 1
  71.     s = s & "人人"
  72. Case 2
  73.     s = s & "人 "
  74. Case 3
  75.     s = s & "人鬼"
  76. Case 4
  77.     s = s & " 鬼"
  78. Case 5
  79.     s = s & "鬼鬼"
  80. Case 6
  81.     s = s & "人人人"
  82. Case 7
  83.     s = s & "人人鬼"
  84. Case 8
  85.     s = s & "鬼鬼鬼"
  86. End Select

  87. If iStep Mod 2 = 0 Then
  88.     s = s & " -> "
  89. Else
  90.     s = s & "    "
  91. End If

  92. Debug.Print s & Left(String(Peoples - b(0, iStep), "人") & "   ", Peoples) & Left(String(Peoples - b(1, iStep), "鬼") & "   ", Peoples)
  93. End Sub
複製代碼
說明:

1.跟過河問題(一)來比較,本程式並沒有修改多少程式碼,只是增加幾個符合條件,以及修改過河人數這個參數罷了,對於相同類型的程式升級來,可謂快速升級。
2.請在開發程式前就先將常數資料盡可能用公用變數的方式來宣告,未來對程式升級時會省下許多維護時間。
3.同樣的要求,請勿直接拿程式碼當作業交差了事,還是花點心思弄懂搜尋原理。
4.求出來的解一共有855個,而且所用步驟數都不一樣,故有所謂「最佳路徑」解的說法,也就是說,在同樣的條件底下,所用的次數越少則愈佳!

本文同步刊載於:http://tw.myblog.yahoo.com/shege-1975/article?mid=2013
1

評分次數

返回列表 回復 發帖