传教士与食人者问题-启发式搜索算法(python)

前言

        传教士(missionaries)与食人者(cannibals)问题是出处于第一篇从分析角度处理问题表述的论文,故在人工智能中很有名。上周我用暴力搜索来进行了求解,这次基于python,使用启发式算法来寻找一个最优渡河方案,希望对读者有所帮助。若您认为本文有不足之处,欢迎留言。


一、问题描述

         若干传教士和食人者都在河的一岸(以下称为原岸),有一条能载1-2个人的船。请设法使所有人都渡河到河的对岸,并要求在任何地方都不能出现食人者多于传教士的情况。

二、主要函数

1.生成可行转移情况

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])# 船的位置在这里设为 # 2.生成可行转移1 ,可方便后续计算
                p+=1
                        
    actions = np.reshape(actions,(p,3))
    print("可行的转移有%d个"%p)
    print("分别是:\n",actions)
    return p,actions

         以3个传教士和3个食人者为例运行以下代码:

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])# 终态
m = 2
p,actions = Actions(m)

       可得到以下可行转移情况:

 2.启发式搜索

def trans_path(state_0,state_z,actions):
# 初态,终态,可行状态,可行转移
    i = 0 # 队头指针
    queue = np.array([state_0])# 队列
    arrived = False # 作为是否已到达终点的标记
    number = np.array([])# 记录每个状态展开的状态数,用于后续画树状图
    while True: 
        w = queue[i][0]# 1 表示在原岸,0 表示在对岸
        state_s,d_list = np.array([]),np.array([])# 搜索状态和距离记录
        if w == 1:# 若船在原岸
            
            for row in range(len(actions)):# 宽度遍历
                state_T = queue[i] - actions[row]

                    # 验证状态是否符合规则
                if all(state_T>=0):
                    if ((state_T[1]==0)|(state_T[1]==3)) or (state_T[1]==state_T[2]):# 符合规则
                        appeared = False
                        for state in queue:
                            if all(state_T == state):
                                appeared = True
                        if not appeared:
                            d = state_T.sum()
                            state_s = np.append(state_s,state_T)
                            d_list = np.append(d_list,d)# 对应状态离终点的距离
                            
                            if d == 0:# 判断是否到达终点,若无就记录
                                arrived = True
                                break
            if arrived:
                number = np.append(number,len(d_list))# # 记录展开的状态数
                queue = np.append(queue,state_s).reshape((-1,3))
                path_cost = queue.shape[0]# 搜索代价
                
                return path_cost,queue,number
            
            state_s = np.reshape(state_s,(-1,3))
            queue = np.append(queue,state_s[d_list.argsort()]).reshape((-1,3))# 添加新状态
            number = np.append(number,len(d_list))# # 记录展开的状态数
            i+=1 # 移动指针

        else:# 若船在对岸
            
            for row in range(len(actions)):# 宽度遍历
                state_T = queue[i] + actions[row]
                # 验证状态是否符合规则
                if all(state_T<= 3):
                    if ((state_T[1]==0)|(state_T[1]==3)) or (state_T[1]==state_T[2]):# 符合规则
                        appeared = False
                        for state in queue:
                            if all(state_T == state):
                                appeared = True
                        if not appeared:
                            d = state_T.sum()
                            state_s = np.append(state_s,state_T)
                            d_list = np.append(d_list,d)# 对应状态离终点的距离
            
            state_s = np.reshape(state_s,(-1,3))
            queue = np.append(queue,state_s[d_list.argsort()]).reshape((-1,3))# 添加新状态
            number = np.append(number,len(d_list))# # 记录展开的状态数
            i+=1 # 移动指针   

        运行函数则可得搜索代价和状态。

path_cost,state_ed,number = trans_path(state_0,state_z,actions)
print("搜索代价为:",path_cost)
print("搜索到的状态有和对应展开的节点数:")
for i in range(len(number)):
    print(state_ed[i],int(number[i]))
for i in range(len(number),len(state_ed)):
    print(state_ed[i])

        输出结果:

 

        根据对应的展开节点数,我们还可以画出启发式搜索的树图。


三、优缺点分析

        启发式算法,不需要列出所有的转移状态,只要事先知道初态和终态,以及中间可行的转移状态需要满足的条件即可。

        只是一般启发式算法一次只能得到一个最优解。


四、完整代码

import numpy as np
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])# 船的位置在这里设为 # 2.生成可行转移1 ,可方便后续计算
                p+=1
                        
    actions = np.reshape(actions,(p,3))
    print("可行的转移有%d个"%p)
    print("分别是:\n",actions)
    return p,actions

def trans_path(state_0,state_z,actions):
# 初态,终态,可行状态,可行转移
    i = 0 # 队头指针
    queue = np.array([state_0])# 队列
    arrived = False # 作为是否已到达终点的标记
    number = np.array([])# 记录每个状态展开的状态数,用于后续画树状图
    while True: 
        w = queue[i][0]# 1 表示在原岸,0 表示在对岸
        state_s,d_list = np.array([]),np.array([])# 搜索状态和距离记录
        if w == 1:# 若船在原岸
            
            for row in range(len(actions)):# 宽度遍历
                state_T = queue[i] - actions[row]

                    # 验证状态是否符合规则
                if all(state_T>=0):
                    if ((state_T[1]==0)|(state_T[1]==3)) or (state_T[1]==state_T[2]):# 符合规则
                        appeared = False
                        for state in queue:
                            if all(state_T == state):
                                appeared = True
                        if not appeared:
                            d = state_T.sum()
                            state_s = np.append(state_s,state_T)
                            d_list = np.append(d_list,d)# 对应状态离终点的距离
                            
                            if d == 0:# 判断是否到达终点,若无就记录
                                arrived = True
                                break
            if arrived:
                number = np.append(number,len(d_list))# # 记录展开的状态数
                queue = np.append(queue,state_s).reshape((-1,3))
                path_cost = queue.shape[0]# 搜索代价
                
                return path_cost,queue,number
            
            state_s = np.reshape(state_s,(-1,3))
            queue = np.append(queue,state_s[d_list.argsort()]).reshape((-1,3))# 添加新状态
            number = np.append(number,len(d_list))# # 记录展开的状态数
            i+=1 # 移动指针

        else:# 若船在对岸
            
            for row in range(len(actions)):# 宽度遍历
                state_T = queue[i] + actions[row]
                # 验证状态是否符合规则
                if all(state_T<= 3):
                    if ((state_T[1]==0)|(state_T[1]==3)) or (state_T[1]==state_T[2]):# 符合规则
                        appeared = False
                        for state in queue:
                            if all(state_T == state):
                                appeared = True
                        if not appeared:
                            d = state_T.sum()
                            state_s = np.append(state_s,state_T)
                            d_list = np.append(d_list,d)# 对应状态离终点的距离
            
            state_s = np.reshape(state_s,(-1,3))
            queue = np.append(queue,state_s[d_list.argsort()]).reshape((-1,3))# 添加新状态
            number = np.append(number,len(d_list))# # 记录展开的状态数
            i+=1 # 移动指针   

ms_0,cs_0 = 3,3# 初始传教士和食人者的数量
state_0 = np.array([1,ms_0,cs_0])# 初态
state_z = np.array([0,0,0])# 终态
m = 2
p,actions = Actions(m)
path_cost,state_ed,number = trans_path(state_0,state_z,actions)
print("搜索代价为:",path_cost)
print("搜索到的状态有和对应展开的节点数:")
for i in range(len(number)):
    print(state_ed[i],int(number[i]))
for i in range(len(number),len(state_ed)):
    print(state_ed[i])

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值