次の有名な問題があります。
問題:
宣教師 3人と人喰い人 3人が、船で川を渡ろうとしています。船は 2人まで乗れますが、最低 1人いなければ動かせません。ここで、こちらの岸もあちらの岸も、また船の上でも、宣教師の数が人喰い人の数を下回ると喰われてしまうので、そのような仕方は許されません。
さて、6人全員があちらの岸へ渡ることは可能でしょうか?
Ruby で解いてみました。
missionaries_and_cannibals1.rb
M, C = 3, 3
Boat = 2
possiblity = ->(m, c) {
result = []
Boat.downto(1) do |n|
0.upto(n) do |i|
boat_m, boat_c = n - i, i
next if boat_m < boat_c and boat_m.nonzero?
here_m = m - boat_m
here_c = c - boat_c
there_m = M - here_m
there_c = C - here_c
next if here_m < 0 or here_c < 0
next if here_m < here_c and here_m.nonzero?
next if there_m < there_c and there_m.nonzero?
result << [boat_m, boat_c]
end
end
result
}
try = ->(m, c, turn, procedure, memo) {
possiblity.(m, c).each do |x|
next_m, next_c = M - m + x[0], C - c + x[1]
next if x == procedure[-1]
if next_m == M and next_c == C and turn.zero?
p procedure + [x]
else
nxt = turn.zero? ? [M - next_m, C - next_c, turn] : [next_m, next_c, turn]
next if memo.include?(nxt)
try.(next_m, next_c, 1 - turn, procedure + [x], memo + [nxt])
end
end
}
try.(M, C, 0, [], [[M, C, 1]])
深さ優先探索(DFS)ですべての解を求めています。m
c
はそれぞれ岸にいる宣教師、人喰い人の人数です。next_m
next_c
はその反対岸の人数が次にどうなるかを表しています。turn
は0なら行き、1なら帰りです。possibility.()
は岸の状態から舟に乗れる人数のすべての組み合わせを返します。procedure
には舟の状態が入っていって、全員が川を渡れれば答えになります。nxt
は「こちら岸」の次の状態です。調べたパターンは memo
に入れておいて、重複しないようにします。また、船の同じ構成での往復は無意味なので、これも行わないようにします。
結果は次のようです。
[[1, 1], [1, 0], [0, 2], [0, 1], [2, 0], [1, 1], [2, 0], [0, 1], [0, 2], [1, 0], [1, 1]]
[[1, 1], [1, 0], [0, 2], [0, 1], [2, 0], [1, 1], [2, 0], [0, 1], [0, 2], [0, 1], [0, 2]]
[[0, 2], [0, 1], [0, 2], [0, 1], [2, 0], [1, 1], [2, 0], [0, 1], [0, 2], [1, 0], [1, 1]]
[[0, 2], [0, 1], [0, 2], [0, 1], [2, 0], [1, 1], [2, 0], [0, 1], [0, 2], [0, 1], [0, 2]]
解は 4とおりありますが、ほぼ同じでちがいは些細なものです。それぞれの手続きで、中の配列 [a, b] は a が(船に乗る)宣教師の数、b が(船に乗る)人喰い人の数を表しています。いちばん上の解を表にしてみましょう。
移動 |
あちらの岸 |
こちらの岸 |
|
[0, 0] |
[3, 3] |
←[1, 1] |
[1, 1] |
[2, 2] |
→[1, 0] |
[0, 1] |
[3, 2] |
←[0, 2] |
[0, 3] |
[3, 0] |
→[0, 1] |
[0, 2] |
[3, 1] |
←[2, 0] |
[2, 2] |
[1, 1] |
→[1, 1] |
[1, 1] |
[2, 2] |
←[2, 0] |
[3, 1] |
[0, 2] |
→[0, 1] |
[3, 0] |
[0, 3] |
←[0, 2] |
[3, 2] |
[0, 1] |
→[1, 0] |
[2, 2] |
[1, 1] |
←[1, 1] |
[3, 3] |
[0, 0] |
確かに 6人全員があちらの岸へ渡ることができています。
宣教師がひとりもいない場合は(当然ながら)喰べられないというのを上手く使わないといけないわけですね。