文章目录
前言
传教士(missionaries)与食人者(cannibals)问题是出处于第一篇从分析角度处理问题表述的论文,故在人工智能中很有名。本文基于python,使用最简单的遍历搜索算法来寻找最优渡河方案,希望对读者有所启发。
一、问题描述
若干传教士和食人者都在河的一岸(以下称为原岸),有一条能载1-2个人的船。请设法使所有人都渡河到河的对岸,并要求在任何地方都不能出现食人者多于传教士的情况。
二、主要函数
1.生成可行状态
def States(ms_0,cs_0):# ms_0,cs_0 分别为初始传教士和食人者的数量
states = np.array([]).astype(int)# 记录可行的状态
n = 0 # 记录可行状态个数
for i in [0,1]:# 船在原岸为 0 ,在对岸为 1
for ms in range(ms_0+1):
if (ms==1) or (ms==2):# 保证有传教士在的任何地方,其人数都不少于食人者的人数
cs = ms
states = np.append(states,[i,ms,cs])
n+=1
else:
for cs in range(cs_0+1):
states = np.append(states,[i,ms,cs])
n+=1
states = np.reshape(states,(n,3))
print("可行的状态有%d个"%n)
print("分别是:\n",states)
return n,states
'''部分状态可能不会存在,但不影响计算转移过程,故未删去'''
2.生成可行转移
def Actions(m):# m 为船的最大载客量
actions = np.array([]).astype(int)# 记录可行的转移
p = 0 # 记录可行转移个数
for ms in range(m+1):
for cs in range(m+1):
if (ms+cs)>=1 and (ms+cs) <= m :
actions = np.append(actions,[1,ms,cs])# 船的位置在这里设为 1 ,可方便后续计算
p+=1
actions = np.reshape(actions,(p,3))
print("可行的转移有%d个"%p)
print("分别是:\n",actions)
return p,actions
3.寻找最优转移路径
def trans_path(state_0,state_z,states,actions):
# 初态,终态,可行状态,可行转移
l = 1 # 已出现的状态有多少个,而状态转移次数为 l-1
state_s = np.array([[state_0]])# 转移路径
state_1 = state_0
action_s = actions.copy()# 建立副本,防破坏输入
while any(state_1 != state_z) and action_s.shape[0] > 0:
i = state_1[0]# 1 表示在原岸,0 表示在对岸
fit = False
if i == 1:
while action_s.shape[0] > 0:# 无放回随机取样
row = np.random.choice(action_s.shape[0])
action = action_s[row]
state_2 = state_1 - action
if all(state_2>=0):# 验证状态是否符合规则
for j in range(states.shape[0]):
if all(state_2 == states[j]):
fit = True
break
if fit:# 符合则跳出循环
break
action_s = np.delete(action_s,row,0)
else:
while action_s.shape[0] > 0:
row = np.random.choice(action_s.shape[0])
action = action_s[row]
state_2 = state_1 + action
if all(state_2<=3):
no_repeat = True
for j in range(states.shape[0]):
if all(state_2 == states[j]):
fit = True
break
if fit:
break
action_s = np.delete(action_s,row,0)
if action_s.shape[0] > 0:# 转换到下一状态
l+=1
state_s = np.append(state_s,state_2)
states = np.delete(states,j,0) # 移除已出现状态,避免重复
state_1 = state_2
action_s = actions.copy()
if action_s.shape[0] > 0:
state_s = np.reshape(state_s,(l,3))
return l,state_s
else: # 此处只是为了防止后续报错,可保证了运行该函数后有有返回值
return 20,state_s
三、测试运行
我们以3个传教士和3个食人者进行举例测试
1.生成可行状态
import numpy as np
ms_0,cs_0 = 3,3# 初始传教士和食人者的数量
state_0 = np.array([1,ms_0,cs_0])# 初态
state_z = np.array([0,0,0])# 终态
n,states = States(ms_0,cs_0)
m = 2 # 船最大承载人数
p,actions = Actions(m)
结果如下:
2.暴力搜索
L = np.array([])
State_s = np.array([])
n = 0 # 记录最短转移路径的所有可能次数
for i in range(100):# 循环100次,以找出最短状态转移
l,state = trans_path(state_0,state_z,states,actions)
L = np.append(L,l)
repeat = np.zeros(int(min(L)))
for i in range(100):# 循环100次,以找出所有最短状态转移
l,state = trans_path(state_0,state_z,states,actions)
if l == min(L):
for j in range(n):
repeat = np.zeros(l)
for k in range(l):
if all(state[k] == State_s[j][k]):
repeat[k] = True
if repeat.all():
break
if not repeat.all():
n+=1
State_s = np.append(State_s,state)
State_s = np.reshape(State_s,(n,l,3))
print("共有%d种转移方法。"%n)
print("状态转移过程分别是:\n",State_s)
结果如下:
总结
我们可以发现在本例中,对于最优转移步骤,除了第一步状态转移和倒数第二步状态转移有两种选择以外,其他状态转移过程都有且仅有一种状态,故共有4种最优转移方案。