問題
在河的左岸有N個傳教士、N個野人和一條船,傳教士們想用這條船把所有人都運過河去,但有以下條件限制:
(1)修道士和野人都會划船,但船每次最多只能運M個人;
(2)在任何岸邊以及船上,野人數目都不能超過修道士,否則修道士會被野人吃掉。
假定野人會服從任何一種過河安排,請規划出一個確保修道士安全過河的計划。
分析
百度一下,網上全是用左岸的傳教士和野人人數以及船的位置這樣一個三元組作為狀態,進行考慮,千篇一律。
我換了一種考慮,只考慮船的狀態。
船的狀態:(x, y) x表示船上x個傳教士,y表示船上y個野人,其中 |x|∈[0, m], |y|∈[0, m], 0<|x|+|y|<=m, x*y>=0, |x|>=|y|
船從左到右時,x,y取非負數。船從右到左時,x,y取非正數
解的編碼:[(x0,y0), (x1,y1), ..., (xp,yp)] 其中x0+x1+...+xp=N, y0+y1+...+yp=N
解的長度不固定,但一定為奇數
開始時左岸(N, N), 右岸(0, 0)。最終時左岸(0, 0), 右岸(N, N)
由於船的合法狀態是動態的、二維的。因此,使用一個函數get_states()來專門生成其狀態空間,使得主程序更加清晰。
代碼
n = 3 # n個傳教士、n個野人
m = 2 # 船能載m人
x = [] # 一個解,就是船的一系列狀態
X = [] # 一組解
is_found = False # 全局終止標志
# 計算船的合法狀態空間(二維)
def get_states(k): # 船准備跑第k趟
global n, m, x
if k%2==0: # 從左到右,只考慮原左岸人數
s1, s2 = n - sum(s[0] for s in x), n - sum(s[1] for s in x)
else: # 從右到左,只考慮原右岸人數(將船的歷史狀態累加可得!!!)
s1, s2 = sum(s[0] for s in x), sum(s[1] for s in x)
for i in range(s1 + 1):
for j in range(s2 + 1):
if 0 < i+j <= m and (i*j == 0 or i >= j):
yield [(-i,-j), (i,j)][k%2==0] # 生成船的合法狀態
# 沖突檢測
def conflict(k): # 船開始跑第k趟
global n, m, x
# 若船上載的人與上一趟一樣(會陷入死循環!!!!)
if k > 0 and x[-1][0] == -x[-2][0] and x[-1][1] == -x[-2][1]:
return True
# 任何時候,船上傳教士人數少於野人,或者無人,或者超載(計算船的合法狀態空間時已經考慮到了。)
#if 0 < abs(x[-1][0]) < abs(x[-1][1]) or x[-1] == (0, 0) or abs(sum(x[-1])) > m:
# return True
# 任何時候,左岸傳教士人數少於野人
if 0 < n - sum(s[0] for s in x) < n - sum(s[1] for s in x):
return True
# 任何時候,右岸傳教士人數少於野人
if 0 < sum(s[0] for s in x) < sum(s[1] for s in x):
return True
return False # 無沖突
# 回溯法
def backtrack(k): # 船准備跑第k趟
global n, m, x, is_found
if is_found: return # 終止所有遞歸
if n - sum(s[0] for s in x) == 0 and n - sum(s[1] for s in x) == 0: # 左岸人數全為0
print(x)
is_found = True
else:
for state in get_states(k): # 遍歷船的合法狀態空間
x.append(state)
if not conflict(k):
backtrack(k+1) # 深度優先
x.pop() # 回溯
# 測試
backtrack(0)
效果圖

解的解釋,從上往下看:

一個結論
貌似只有滿足m = n-1,此問題才有解。
