修道士野人问题的python求解

  • 问题描述
    • 状态空间表示
    • 程序设计
    • 执行结果
    • 源代码(Python3.5)

github项目文件下载点这里~

问题描述

修道士(Missionaries)和野人(Cannibals)问题
修道士野人过河问题是人工智能领域一个典型的问题,其经典描述为如下。
在河的左岸有N个传教士(M)、N个野人(C)和一条船(Boat),修道士们想用这条船把所有人都运过河去,但有以下条件限制:

  1. 修道士和野人都会划船,但船每次最多只能运K个人;
  2. 在任何岸边野人数目都不能超过修道士,否则修道士会被野人吃掉。
    上述约束可以表述为:
    ① M≧C 任何时刻两岸、船上都必须满足传教士人数不少于野人数(M=0时除外,既没有传教士);
    ② M+C≦K 船上人数限制在K以内。

状态空间表示

(一)状态空间
在此问题的求解中,选择使用三元组(M, C, S)表示问题的状态,式中M表示起始岸修道士人数,C表示起始岸野人人数,S为0-1变量,表示船的位置,当S为0时表示船在起始岸,为1时表示船在终点岸。如:(0,3,0)表示起始岸有三个野人,没有修道士,船在起始岸。

于是修道士野人问题可以描述为: 从(3,3,0)到(0,0,1)的状态转换。在此问题的状态空间中共有32 种状态,其中12种不合理状态:如(1,0,1)说明右岸有2个M,3个C;4种不可能状态:如(3,3,0)说明所有M和C都在左岸,而船在右岸,所以可用的状态共16种,组成合理的状态空间。可能的问题状态分别为:(0, 0, 1), (0, 1, 1), (0, 2, 1), (1, 0, 1), (1, 1, 0), (1, 1, 1), (1, 2, 0), (1, 2, 1), (1, 3, 0), (2, 0, 1), (2, 1, 0), (2, 1, 1), (2, 2, 0), (2, 2, 1), (2, 3, 0), (3, 1, 0), (3, 2, 0), (3, 3, 0)。

(二)操作集
在此问题种定义Operation操作算符,用二元组(M, C)表示,式中M表示一次划船过程中船上修道士人数,C表示穿上野人人数。并规定,每一次使用操作算符对问题状态进行操作运算后,问题状态的S必须改变,即船必须从一岸驶向另一岸。例如,当对初始问题状态(3,3,0)使用(1,1)算符后,问题状态变成(2,2,1),这表示一个修道士和一个野人划船驶到终点岸,此时船停留在重点岸。根据左右两岸和穿上都不能出现野人人数大于修道士人数的约束,可以得到可用的操作算符共有5种,分别是(0, 1), (0, 2), (1, 0), (1, 1), (2, 0)。

程序设计

(一)存储结构
1.基本问题状态和操作算符使用列表存储。由于本问题仅需使用到简单的数据插入、提取等功能,因此可用选用Python提供的基本数据结构列表完成。使用列表表示问题的状态空间为:[[0, 0, 1], [0, 1, 1], [0, 2, 1], [1, 0, 1], [1, 1, 0], [1, 1, 1], [1, 2, 0], [1, 2, 1], [1, 3, 0], [2, 0, 1], [2, 1, 0], [2, 1, 1], [2, 2, 0], [2, 2, 1], [2, 3, 0], [3, 1, 0], [3, 2, 0], [3, 3, 0]];操作集为:[[0, 1], [0, 2], [1, 0], [1, 1], [2, 0]];

2.节点关系使用字典存储。在找到目标节点后,需要回溯从开始节点走到当前节点的路径,这需要存储节点间的关系。这里使用字典存储节点间的亲属关系,子节点为键,父节点为值。

(二)算法基本思想
1.首先将问题初始状态(initial_state)节点放入OPEN表中(用于临时存储);
2.从OPEN表中取出首节点,判断是否为目标节点。若为目标节点,则任务完成,输出结果;若不是目标节点,则进行下一步;
3.对从OPEN表中取出的不是目标节点的当前节点进行可扩展性判断,之后根据所使用的搜索策略将其子节点存入OPEN表;若当前节点不可扩展,则程序结束,问题无解。

(三)搜索策略
本次使用广度优先算法、有界深度优先算法和全局启发式搜索算法分别对问题进行求解。其中全局启发式搜索算法所使用的启发式函数为f(n) = d(n) + w(n), d(n)为当前节点深度,w(n)为未渡河人数;有界深度优先算法设置深度阈值为25。

(四)函数设计与调用关系
1.函数设计
此程序共设计并使用到10个自定义函数,其中:
(1)create_vertex()和create_edges()用于初始化问题条件,生成相应的节点和边;
(2)move()用于对问题状态执行操作算符并控制移动过程中各状态的合理性(如避免出现不合理状态,控制船只状态等);
(3)whether_expandable()用于判断当前节点是否可扩展;
(4)get_d(), get_w()和search_heuristic()共同实现启发式搜索。get_d()用于获取当前节点深度,get_w()用于计算当前节点距离目标节点的距离,即还有多少人尚未过河;
(5)search_depth()深度优先搜索算法;
(6)search_breadth()广度优先搜索算法;
(7)heap_adjust(), heap_create()和heap_sort()用于实现堆排序功能,此功能用于给生成的子节点进行排序。

2.函数调用关系
程序中函数调用关系为:
(1) main()->create_vertex(), create_edges(), 搜索算法(三选一);
(2) search_breadth()->whether_expandable()->move();
(3) search_ depth ()->whether_expandable()->move();
(4) search_heuristic()->whether_expandable()->move();
search_heuristic()->get_d(), get_w();
search_heuristic()->heap_sort()->heap_adjust(), heap_create()。

执行结果

(一) 启发式索搜
可用的操作算符为: [[0, 1], [0, 2], [1, 0], [1, 1], [2, 0]]
可能出现的顶点有 16 种, 分别为: [[0, 0, 1], [0, 1, 1], [0, 2, 1], [1, 0, 1], [1, 1, 0], [1, 1, 1], [1, 2, 0], [1, 2, 1], [1, 3, 0], [2, 0, 1], [2, 1, 0], [2, 1, 1], [2, 2, 0], [2, 2, 1], [2, 3, 0], [3, 1, 0], [3, 2, 0], [3, 3, 0]]
渡河成功!路径为:
[3, 3, 0] ->[2, 2, 1] ->[3, 2, 0] ->[3, 0, 1] ->[3, 1, 0] ->[1, 1, 1] ->[2, 2, 0] ->[0, 2, 1] ->[0, 3, 0] ->[0, 1, 1] ->[1, 1, 0] ->[0, 0, 1]

(二) 广度优先搜索
可用的操作算符为: [[0, 1], [0, 2], [1, 0], [1, 1], [2, 0]]
可能出现的顶点有 16 种, 分别为: [[0, 0, 1], [0, 1, 1], [0, 2, 1], [1, 0, 1], [1, 1, 0], [1, 1, 1], [1, 2, 0], [1, 2, 1], [1, 3, 0], [2, 0, 1], [2, 1, 0], [2, 1, 1], [2, 2, 0], [2, 2, 1], [2, 3, 0], [3, 1, 0], [3, 2, 0], [3, 3, 0]]
渡河成功!路径为:
[3, 3, 0] ->[3, 1, 1] ->[3, 2, 0] ->[3, 0, 1] ->[3, 1, 0] ->[1, 1, 1] ->[2, 2, 0] ->[0, 2, 1] ->[0, 3, 0] ->[0, 1, 1] ->[0, 2, 0] ->[0, 0, 1]

(三) 有界深度搜索
可用的操作算符为: [[0, 1], [0, 2], [1, 0], [1, 1], [2, 0]]
可能出现的顶点有 16 种, 分别为: [[0, 0, 1], [0, 1, 1], [0, 2, 1], [1, 0, 1], [1, 1, 0], [1, 1, 1], [1, 2, 0], [1, 2, 1], [1, 3, 0], [2, 0, 1], [2, 1, 0], [2, 1, 1], [2, 2, 0], [2, 2, 1], [2, 3, 0], [3, 1, 0], [3, 2, 0], [3, 3, 0]]
渡河成功!路径为:
[3, 3, 0] ->[3, 1, 1] ->[3, 2, 0] ->[3, 0, 1] ->[3, 1, 0] ->[1, 1, 1] ->[2, 2, 0] ->[0, 2, 1] ->[0, 3, 0] ->[0, 1, 1] ->[0, 2, 0] ->[0, 0, 1]

源代码(Python3.5)

(一)main文件

from initial import create_edges
from initial import create_vertex
from search_breadth import search_breadth
from search_depth import search_depth
from search_heuristic import search_heuristiccapacity = 2  # 小船容载量,过低则可能导致问题无解
Missionaries = 3  # 修道士的人数
Cannibals = 3  # 野人人数
init_state = [Missionaries, Cannibals, 0]    # 问题初始状态,表示三个修道士、三个野人和小船都在初始岸
layer = 25  # 设置有界深度算法深度阈值
set_of_operation = create_edges(capacity)  # 生成所有可用的运算符,即图中存在的边
set_of_vertex = create_vertex(Missionaries, Cannibals)  # 生成所有可能的问题状态,即图中存在的顶点# search_breadth(init_state, set_of_operation)  # 利用广度优先算法求解
# search_depth(init_state, set_of_operation, layer)   # 利用有界深度算法求解
search_heuristic(init_state, set_of_operation)   # 利用有界深度算法求解

(二)initial文件

def create_vertex(missionary, cannibal):  # 生成问题中所有可能的状态,即所有顶点init_state = [missionary, cannibal, 0]  # 初始状态set_of_state = []  # 存储状态集的列表count = 0for i in range(missionary+1):  # 生成所有可能的状态,即所有顶点for j in range(cannibal+1):if init_state[0] == 0 or init_state[0] >= init_state[1]:if i != 0 and j != 0:set_of_state.append([i, j, 0])if i != missionary and j != cannibal:set_of_state.append([i, j, 1])count += 1print('可能出现的顶点有', count, '种, 分别为:', set_of_state)def create_edges(capacity):  # 生成所有的运算子,即边set_of_operation = []  # 存储运算子的列表for i in range(capacity + 1):for j in range(capacity + 1):if i + j <= capacity and (i >= j or i == 0):if i == 0 and j == 0:continueset_of_operation.append([i, j])print('可用的操作算符为:', set_of_operation)return set_of_operation

(三)move文件

def move(vertex, edge, init_missionary=3, init_cannibal=3):if vertex[2] == 1:missionary = vertex[0] + edge[0]cannibal = vertex[1] + edge[1]state_of_boat = 1 - vertex[2]else:missionary = vertex[0] - edge[0]cannibal = vertex[1] - edge[1]state_of_boat = 1 - vertex[2]if missionary != 0 and missionary < cannibal:return Falseelif (init_missionary - missionary) != 0 and ((init_missionary - missionary) < (init_cannibal - cannibal)):return Falseelif missionary < 0 or cannibal < 0 or (init_missionary - missionary) < 0 or (init_cannibal - cannibal) < 0:return Falseelse:return [missionary, cannibal, state_of_boat]

(四)heap_sort文件

def heap_adjust(lists, pos, length):  # 堆排序(升序排列,构建大根堆):max_ = poslchild = 2*pos+1  # 由于lists下表从0开始,所以左右孩子下标为2*pos+1,2*pos+2rchild = 2*pos+2if max_ < length // 2:  # 注意符号是<,堆调整时,必定是从(length//2)-1开始if lchild < length and lists[lchild][3] > lists[max_][3]:max_ = lchildif rchild < length and lists[rchild][3] > lists[max_][3]:max_ = rchildif max_ != pos:  # 如果max_未发生改变,说明不需要调整lists[max_], lists[pos] = lists[pos], lists[max_]heap_adjust(lists, max_, length)  # 递归调整def heap_create(lists, length):for i in range(length // 2)[::-1]:heap_adjust(lists, i, length)def heap_sort(lists):length = len(lists)heap_create(lists, length)for i in range(length)[::-1]:lists[0], lists[i] = lists[i], lists[0]  # 首尾元素互换,将最大的元素放在列表末尾heap_adjust(lists, 0, i)  # 从头再调整,列表长度-1(尾元素已经完成排序,所以列表长度-1)

(五)search_breadth文件

from whether_expandable import whether_expandabledef search_breadth(init_state, set_of_operation):  # 广度优先搜索算法open_list = []relation = {}open_list.append(init_state)while 1:if open_list == []:  # 判断open表是否为空print("失败!open表为空,不存在可用节点,无解。")returnvertex = open_list[0]open_list = open_list[1: -1]  # 更新open表if vertex == [0, 0, 1]:  # 当前节点为目标节点,打印输出result = []  # 存储整个路径result.append(vertex)res = []   # 存储路径中的单个节点print("渡河成功!路径为:")while res != init_state:res = relation[str(result[-1])]if res:result.append(res)else:breakfor i in result[::-1]:if i != result[0]:print(i, '->', end='')else:print(i)returnelse:  # 当前节点不是目标节点时if vertex != init_state:sons = whether_expandable(vertex, set_of_operation, relation[str(vertex)])else:sons = whether_expandable(vertex, set_of_operation, [0, 0, 0])if sons:  # 判断当前节点是否可扩展for i in sons:relation[str(i)] = vertex  # 用字典存储节点间的亲属关系,子节点为键,父节点为值open_list.append(i)

(六)search_depth文件

from whether_expandable import whether_expandabledef search_depth(init_state, set_of_operation, layer):  # 深度有界搜索算法open_list = []relation = {}open_list.append(init_state)layer_count = 0while 1:layer_count += 1if layer_count > layer:  # 使用layer来判断迭代深度,当深度超过预设值时,停止循环print("深度超过最大限度(%d),未找到解!", layer)breakif open_list == []:  # 判断open表是否为空print("失败!open表为空,不存在可用节点,无解。")returnvertex = open_list[0]open_list = open_list[1: -1]  # 更新open表if vertex == [0, 0, 1]:result = []  # 存储整个路径result.append(vertex)res = []  # 存储路径中的单个节点print("渡河成功!路径为:")while res != init_state:res = relation[str(result[-1])]if res:result.append(res)else:breakfor i in result[::-1]:if i != result[0]:print(i, '->', end='')else:print(i)returnelse:if vertex != init_state:sons = whether_expandable(vertex, set_of_operation, relation[str(vertex)])else:sons = whether_expandable(vertex, set_of_operation, [0, 0, 0])if sons:  # 判断当前节点是否可扩展for i in sons:relation[str(i)] = vertex  # 用字典存储节点间的亲属关系,子节点为键,父节点为值open_list.insert(0, i)

(七)search_heuristic文件

from whether_expandable import whether_expandable   # 启发函数为f(n) = d(n) + w(n), d(n)为当前节点深度,w(n)为未渡河人数
from heap_sort import heap_sortdef get_d(vertex, init_state, relation):  # 用于计算当前节点的深度result = []  # 存储整个路径result.append(vertex)res = []  # 存储路径中的单个节点d = 0# print("渡河成功!路径为:")while res != init_state:d += 1res = relation[str(result[-1])]if res:result.append(res)else:breakreturn ddef get_w(vertex):  # 用于计算当前节点距离目标节点的距离,即还有多少人尚未过河return vertex[0] + vertex[1] + 1 - vertex[2]def search_heuristic(init_state, set_of_operation):open_list = []relation = {}open_list.append(init_state)while 1:if open_list == []:  # 判断open表是否为空print("失败!open表为空,不存在可用节点,无解。")returnvertex = open_list[0]open_list = open_list[1: -1]  # 更新open表if vertex == [0, 0, 1]:result = []  # 存储整个路径result.append(vertex)res = []  # 存储路径中的单个节点print("渡河成功!路径为:")while res != init_state:res = relation[str(result[-1])]if res:result.append(res)else:breakfor i in result[::-1]:if i != result[0]:print(i, '->', end='')else:print(i)returnelse:if vertex != init_state:sons = whether_expandable(vertex, set_of_operation, relation[str(vertex)])else:sons = whether_expandable(vertex, set_of_operation, [0, 0, 0])if sons:  # 判断当前节点是否可扩展sort_list = []for i in sons:relation[str(i)] = vertex  # 用字典存储节点间的亲属关系,子节点为键,父节点为值i.append(get_d(i, init_state, relation) + get_w(i))  # 使用启发函数对生成的子节点进行标注,并将标注的权值加到子节点列表内sort_list.append(i)heap_sort(sort_list)for i in sort_list:i = i[:-1]open_list.append(i)

(八)whether_expandable文件

from move import movedef whether_expandable(vertex, set_of_operation, pre_vertex):  # 判断当前节点是否可扩展sons = []for operation in set_of_operation:m = move(vertex, operation)if m:if m != pre_vertex:  # 扩展得到的子节点不应该是当前节点的父节点,即应当避免重复sons.append(m)if sons == []:return Falseelse:return sons

修道士野人问题的python求解相关推荐

  1. prolog学习_修道士野人问题

    只贴代码跟样例输出: move(1,0).%表示船上有一位牧师,没有野人. move(0,1). move(0,2). move(2,0). move(1,1). legal((X,Y,_)):- % ...

  2. python深度优先搜索传教士和野人_ai1 带回溯的深度优先策略:解决经典野人传教士过河问题的求解:三个修道士和三个野人过河 - 下载 - 搜珍网...

    带回溯的深度优先策略:解决经典野人传教士过河问题的求解:三个修道士和三个野人过河,船一次最多只能载两个人,在任何时候修道士的人数不能少于野人人数,否则野人会吃掉修道士.找出六个人顺利过河的所有方案. ...

  3. 用状态空间方法求解修道士与野人问题

    目录 一.状态空间表示法回顾 1.问题状态空间的构成 2.用状态空间表示问题的步骤 二.使用状态空间法求解修道士与野人问题 1.问题描述 2.状态空间求解步骤 总结 一.状态空间表示法回顾 状态空间表 ...

  4. 修道士和野人问题:所有解、启发求解、简单界面

    一.作业任务 修道士和野人问题:设有三个修道士和3个野人来到河边,打算用一条船从河的左岸渡到河的右岸去.但该船每次只能装载两个人,在任何岸边野人的数目都不得超过修道士的人数,否则修道士就会被野人吃掉. ...

  5. 使用dfs求解修道士和野人问题

    原文链接: 使用dfs求解修道士和野人问题 上一篇: js Set 的使用 下一篇: VMware 挂载U盘 1.问题描述 :这是一个古典问题.假设有n个道士和n个野人准备渡河.但只有一条能容纳c人的 ...

  6. 人工智能实践作业-修道士和野人过河问题

    人工智能实践作业-修道士和野人过河问题: 用编程语言编写和调试一个基于深度优先搜索法的解决"野人与传教士过河"问题的程序.目的是学会运用知识表示方法和搜索策略求解一些考验智力的简单 ...

  7. 1. A星算法解决修道士与野人问题

    A星算法解决修道士与野人问题 1. 运行环境 CPU:I5-10400 内存:16GB 系统:Win10 64位专业版,20H2 IDE:Vistual Studio 2019专业版 2. 问题描述 ...

  8. 状态空间表示法----野人与修道士

    状态空间法的应用 修道士(Missionaries)和野人(Cannibals)问题 在河的左岸有N个传教士(M).N个野人(C)和一条船(Boat),传教士们想用这条船把所有人都运过河去,但有以下条 ...

  9. 修道士与野人问题——C++源代码,伪代码,详细分析

    前言:这一个经典的问题,可以把问题转换成数据结构中的 图 来解决.本博客节选自我去年7月份的数据结构报告 问题描述 假设有 n 个修道士和 n 个野人准备渡河,但只有一条能容纳 c 人的小船,为了防止 ...

最新文章

  1. 【HDU 5402】Travelling Salesman Problem(构造)
  2. 实例讲解《Microsoft AJAX Library》(2):DomEvent类
  3. Java中对象的复制
  4. o型圈沟槽设计_深圳综合O型密封圈ID544.4MM*8.6MM报价-星湖蓝海科技
  5. IoT与大数据 如何激发数字营销最大潜能?
  6. java设计按月每天签到_活动攻略|新同学新签到,欢乐福利全都要!
  7. 利用计算机卸载,电脑使用痕迹彻底清理工具(无影无踪WYWZ)
  8. 信息学奥赛一本通 1078:求分数序列和 | OpenJudge NOI 1.5 32
  9. c# 结构体 4字节对齐_C语言程序员们常说的“内存对齐”,究竟有什么目的?
  10. 邮件服务器两种协议,邮件服务器协议
  11. python install causes ModuleNotFoundError: No module named ‘_swigfaiss‘
  12. 使用 windows命令和iconv.exe批量转换文件编码
  13. java模板导出excel_POI导出excel模板三种方式
  14. 图片和文本置顶显示的方法
  15. npm WARN deprecated bfj-node4@5.3.1: Switch to the `bfj` package for fixes and new features
  16. redis的几种常见客户端
  17. 恢复计算机管理员权限软件,帮您修复win10系统管理员权限的恢复步骤
  18. idea查看每行代码是谁修改的
  19. 【51单片机】矩阵键盘逐行扫描法仿真实验+超详细Proteus仿真和Keil操作步骤
  20. 中国智慧民航行业现状分析与前景规划咨询报告2022-2028年版

热门文章

  1. 用python发送163邮件
  2. python预处理实例_Python----数据预处理代码实例
  3. 医学图像处理相关代码分享
  4. 旅游企业财务管理【2】
  5. java的万里长征之第一步
  6. java界面布局举例,java图形界面实例
  7. spring mvc + JSR-303验证框架
  8. 软考知识点---03校验码
  9. 03教育与社会的发展
  10. vue 防抖节流,开箱即用