在河的左岸有三个修道士,三个野人和一条船,修道士想用这条船把所有的人都运到河对岸,但要受到以下条件限制:修道士都会划船,但船一次只能装运两个人。在任何岸边野人数不能超过修道士,否则修道士会被野人吃掉。
本文采用采用Python语言实现问题求解,用三元组表示渡河过程中的状态,并用箭头表明这些状态间的迁移。
在图搜索算法中,如果能在每一步都利用估价函数f(n)=g(n)+h(n)对open表中的节点进行排序,则该搜索算法为A算法。
如果对A算法中的g(n)和h(n)分别提出如下限制:
则称得到的算法为A*算法。
如果用M表示左岸的修道士数,C表示左岸的野人数,B表示左岸的船数,K表示船的最大载人数,用三元组(M,C,B)表示问题的状态。确定估价函数,设g(n)=d(n),h(n)=M+C-K*B,f(n)=d(n)+M+C-K*B,其中d(n)为节点深度。h(n)为船只每次从左岸运走K*B个人之后左岸剩余的人数,而从右岸返回左岸,左岸人数又会增加,因此,左岸剩余人数不少于h(n),满足h(n)<=h*(n),符合A*算法的限制条件。
程序要求手动输入初始左岸传教士、野人人数以及船的最大载人数,因此在设计中需要设定三个输入,而输出,我给出的是最佳的方案以及搜索树,最佳方案包括每一步的人数和船的状态以及相应的h值和f值,搜索树中包括不同的深度以及每一层包含的状态
程序设计完成后,接下来我通过改变估价函数来进行探究实验:
图一 M=3 C=3 K=2
图二 M=5 C=4 K=3
图三 M=5 C=5 K=2
图四 M=5 C=5 K=3
图五 M=4 C=4 K=3
图六 M=5 C=5 K=3
图七 M=4 C=4 K=3
图八 M=4 C=4 K=3
通过实验,程序可以实现根据所给初始条件给出最佳的过河方案,并且在每一步中都会给出相应的状态和和h,f值,并且最后会给出该过程的一个具体的搜索图。算法中每一次计算估价函数值然后进行排序,由于算法为全局择优搜索算法,所以每一次取出所有节点中估价函数中值最小的,如果大小相同,该算法设计的是选取搜索图中步骤靠前的那一个状态,例如图一中深度为10的状态由两个,并且它们的估价函数值大小相同,算法选取靠前的[1 1 1]作为过河方式。由于所给条件不一定能够成功过河所以程序也会给出判断是否能够过河,例如图三,初始条件为[5,5,2]则无解,搜索图中到达深度为9就停止了。而如果扩大船的载人数为3,则如图四,算法仍然可以找到最佳过河方式。从图中我们还可以发现h的值可能小于0,例如图五中在[1 1 1]这个状态下h=-1,但是这其实并不属于非法状态,虽然之前定义h为左岸剩余人数,实际不会小于0,但是还有一个条件,h为运走K*B个人后剩余人数,即最少剩余,实际不一定出现这种情况,再从A*算法本身定义来看,h(n)是h*(n)的下界,是对于h*(n)的一个估计,不一定是从初始节点到节点n的真正最小代价,所以h出现小于0的情况没有问题。
'''
修道士和野人问题(基于A*算法)
'''
def safe(array,M,C,B,m,c):#判断状态是否安全
P = array[:]
if M == m :#左岸传教士数量等于总数
if C >=0 and C <= c :
P.insert(0,[M,C,1-B])
for i in open:
if P[0] == i[0]:
return False
for i in closed:
if P[0] == i[0]:
return False
open.append(P)
return True
else:
return False
elif M > 0 :#左岸传教士数量基于0到N之间时
if C >=0 and M >= C and M <= m and C <= c and m-M >= c-C:
P.insert(0,[M,C,1-B])
for i in open:
if P[0] == i[0]:
return False
for i in closed:
if P[0] == i[0]:
return False
open.append(P)
return True
else:
return False
elif M == 0:#左岸传教士为0
if C >= 0 and C <= c:
P.insert(0, [M, C, 1 - B])
for i in open:
if P[0] == i[0]:
return False
for i in closed:
if P[0] == i[0]:
return False
open.append(P)
return True
else:
return False
else:
return False
def F(this,k,dic):#估价函数计算 f(n) = d(n)+M + C - K * B
return dic[str(this)]+this[0] + this[1] - k * this[2]-dic[str(this)]
def sorted(K,dic):
l=len(open)
for i in range(l):
for j in range(l):
if F(open[i][0],K,dic)dic[str(open[j][0])]:
tmp=open[i]
open[i]=open[j]
open[j]=tmp
if __name__ == '__main__':
d=0
dic={}
M = int(input("传教士人数:"))
C=int(input("野人人数:"))
K =int(input("船的最大载人数:"))
open = [] #创建open表
closed = [] #创建closed表
sample = [M,C,1] #初始状态
dic[str(sample)]=0
goal = [0,0,0]#目标状态
open.append([sample])
while(1):
flag=0
if len(open) == 0:
print("渡河失败")
break
else:
this = open.pop(0)
closed.append(this)
if this[0] == goal:
print("问题有解,最佳方案如下:")
for i in this[::-1]:
h=i[0]+i[1]-K*i[2]
print(i,'h={},f={}'.format(h,dic[str(i)]+h))
break
d+=1
#扩展节点
if this[0][2] == 1 :#船在左岸时
for i in range(1,K+1):
if safe(this,this[0][0]-i,this[0][1],this[0][2],M,C):
dic[str(open[-1][0])]=d
flag=1
for i in range(1,K+1):
if safe(this,this[0][0],this[0][1]-i,this[0][2],M,C):
dic[str(open[-1][0])]=d
flag=1
for i in range(1,K):
for r in range(1,K-i+1):
if safe(this,this[0][0] - i,this[0][1] - r, this[0][2],M,C):
flag=1
dic[str(open[-1][0])]=d
else:#船在右岸时
for i in range(1,K+1):
if safe(this,this[0][0]+i,this[0][1],this[0][2],M,C):
dic[str(open[-1][0])]=d
flag=1
for i in range(1,K+1):
if safe(this,this[0][0],this[0][1]+ i,this[0][2],M,C):
dic[str(open[-1][0])]=d
flag=1
for i in range(1,K):
for r in range(1,K-i+1):
if safe(this,this[0][0] + i,this[0][1] + r, this[0][2],M,C):
dic[str(open[-1][0])]=d
flag=1
if flag==0:
d=d-1
sorted(K,dic)
print('\n搜索图如下(包括状态和深度)')
length=max(dic.values())
for i in range(length+1):
print(i,end=' ')
for j in dic.keys():
if dic[j]==i:
print(j,end=' ')
print()