問題描述
有三個傳教士和三個野人一起來到河邊準備渡河,河邊有一條空船,且傳教士和野人都會划船,但每次最多可供兩人乘渡。河的任何一岸以及船上一旦出現野人人數超過傳教士人數,野人就會把傳教士喫掉。爲安全 地渡河,傳教士應該如何規劃渡河方案?試給出該問題的狀態圖表示,並編程求解之。
若傳教士和野人的數碼均爲 5 人,渡船至多可乘 3 人,請定義一個啓發函數,並給出相應的搜索樹。
解決思路
設 M 爲傳教士總人數,C 爲野人總人數,K 爲每條船乘坐人數,船的狀態表示爲 b。
在 M=3,C=3,K=2 情況下:
令 m 表示在左岸的傳教士人數,c 表示在左岸的野人數,b=1 表示船在左岸, b=0 表示船在右岸。
因此可用(m, c, b)表述當前狀態。
則,初始狀態爲(3, 3, 1), 目標狀態爲(0, 0, 0)。
渡河時,爲確保安全,需滿足:
0 ≤ m ≤ 3,0 ≤ c ≤ 3, b ∈ {0,1}, m > c(m 不爲 0 時), 3-m > 3-c(m 不爲 3 時)
啓發函數:h=m+c-kb
代碼示例:
# -*- coding: utf-8 -*-
import operator
__metaclass__ = type
M = 5 # 傳教士
C = 5 # 野人
K = 3 # 每船乘坐人數
child = [] # child用來存所有的拓展節點
open_list = [] # open表
closed_list = [] # closed表
class State:
def __init__(self, m, c, b):
self.m = m #左岸傳教士數量
self.c = c #左岸野人數量
self.b = b # b = 1: 船在左岸;b = 0: 船在右岸
self.g = 0
self.f = 0 #f = g+h
self.father = None
self.node = [m, c, b]
init = State(M, C, 1) # 初始節點
goal = State(0, 0, 0) # 目標
#0 ≤ m ≤ 3,0 ≤ c ≤ 3, b ∈ {0,1}, 左岸m > c(m 不爲 0 時), 右岸3-m > 3-c(m 不爲 3 時)
def safe(s):
if s.m > M or s.m < 0 or s.c > C or s.c < 0 or (s.m != 0 and s.m < s.c) or (s.m != M and M - s.m < C - s.c):
return False
else:
return True
# 啓發函數
def h(s):
return s.m + s.c - K * s.b
# return M - s.m + C - s.c
def equal(a, b):
if a.node == b.node:
return True
# 判斷當前狀態與父狀態是否一致
def back(new, s):
if s.father is None:
return False
return equal(new, s.father)
# 將open_list以f值進行排序
def open_sort(l):
the_key = operator.attrgetter('f') # 指定屬性排序的key
l.sort(key=the_key)
# 擴展節點時在open表和closed表中找原來是否存在相同mcb屬性的節點
def in_list(new, l):
for item in l:
if new.node == item.node:
return True, item
return False, None
def A_star(s):
global open_list, closed_list
open_list = [s]
closed_list = []
# print ('open list:')
# print ('closed list:') # 選擇打印open表或closed表變化過程
print(s.node)
while open_list: # open表非空
get = open_list[0] # 取出open表第一個元素get
if get.node == goal.node: # 判斷是否爲目標節點
return get
open_list.remove(get) # 將get從open表移出
closed_list.append(get) # 將get加入closed表
# 以下得到一個get的新子節點new並考慮是否放入openlist
for i in range(M): # 上船傳教士
for j in range(C): # 上船野人
# 船上非法情況
if i + j == 0 or i + j > K or (i != 0 and i < j):
continue
if get.b == 1: # 當前船在左岸,下一狀態統計船在右岸的情況
new = State(get.m - i, get.c - j, 0)
child.append(new)
else: # 當前船在右岸,下一狀態統計船在左岸的情況
new = State(get.m + i, get.c + j, 1)
child.append(new)
#優先級:not>and>ture。如果狀態不安全或者要拓展的節點與當前節點的父節點狀態一致。
if not safe(new) or back(new, get): # 狀態非法或new折返了
child.pop()
#如果要拓展的節點滿足以上情況,將它的父親設爲當前節點,計算f,並對open_list排序
else:
new.father = get
new.g = get.g + 1 #與起點的距離
new.f = get.g + h(get) # f = g + h
# 如果new在open表中(只關注m,c,b的值)
if in_list(new, open_list)[0]:
old = in_list(new, open_list)[1]
if new.f < old.f: # new的f<open表相同狀態的f
open_list.remove(old)
open_list.append(new)
open_sort(open_list)
else:
pass
# 繼續,如果new在closed表中
elif in_list(new, closed_list)[0]:
old = in_list(new, closed_list)[1]
if new.f < old.f:
# 將old從closed刪除,並重新加入open
closed_list.remove(old)
open_list.append(new)
open_sort(open_list)
else:
pass
else:
open_list.append(new)
open_sort(open_list)
# 打印open表或closed表
for o in open_list:
# for o in closed_list:
print(o.node)
print
# 遞歸打印路徑
def printPath(f):
if f is None:
return
printPath(f.father)
#注意print()語句放在遞歸調用前和遞歸調用後的區別。放在後實現了倒敘輸出
print(f.node )
if __name__ == '__main__':
print ('有%d個傳教士,%d個野人,船容量:%d' % (M, C, K))
final = A_star(init)
if final:
print ('有解,解爲:')
printPath(final)
else:
print ('無解!')