修道士野人问题的python求解
修道士野人问题的python求解
- 问题描述
- 状态空间表示
- 程序设计
- 执行结果
- 源代码(Python3.5)
github项目文件下载点这里~
问题描述
修道士(Missionaries)和野人(Cannibals)问题
修道士野人过河问题是人工智能领域一个典型的问题,其经典描述为如下。
在河的左岸有N个传教士(M)、N个野人(C)和一条船(Boat),修道士们想用这条船把所有人都运过河去,但有以下条件限制:
- 修道士和野人都会划船,但船每次最多只能运K个人;
- 在任何岸边野人数目都不能超过修道士,否则修道士会被野人吃掉。
上述约束可以表述为:
① 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求解相关推荐
- prolog学习_修道士野人问题
只贴代码跟样例输出: move(1,0).%表示船上有一位牧师,没有野人. move(0,1). move(0,2). move(2,0). move(1,1). legal((X,Y,_)):- % ...
- python深度优先搜索传教士和野人_ai1 带回溯的深度优先策略:解决经典野人传教士过河问题的求解:三个修道士和三个野人过河 - 下载 - 搜珍网...
带回溯的深度优先策略:解决经典野人传教士过河问题的求解:三个修道士和三个野人过河,船一次最多只能载两个人,在任何时候修道士的人数不能少于野人人数,否则野人会吃掉修道士.找出六个人顺利过河的所有方案. ...
- 用状态空间方法求解修道士与野人问题
目录 一.状态空间表示法回顾 1.问题状态空间的构成 2.用状态空间表示问题的步骤 二.使用状态空间法求解修道士与野人问题 1.问题描述 2.状态空间求解步骤 总结 一.状态空间表示法回顾 状态空间表 ...
- 修道士和野人问题:所有解、启发求解、简单界面
一.作业任务 修道士和野人问题:设有三个修道士和3个野人来到河边,打算用一条船从河的左岸渡到河的右岸去.但该船每次只能装载两个人,在任何岸边野人的数目都不得超过修道士的人数,否则修道士就会被野人吃掉. ...
- 使用dfs求解修道士和野人问题
原文链接: 使用dfs求解修道士和野人问题 上一篇: js Set 的使用 下一篇: VMware 挂载U盘 1.问题描述 :这是一个古典问题.假设有n个道士和n个野人准备渡河.但只有一条能容纳c人的 ...
- 人工智能实践作业-修道士和野人过河问题
人工智能实践作业-修道士和野人过河问题: 用编程语言编写和调试一个基于深度优先搜索法的解决"野人与传教士过河"问题的程序.目的是学会运用知识表示方法和搜索策略求解一些考验智力的简单 ...
- 1. A星算法解决修道士与野人问题
A星算法解决修道士与野人问题 1. 运行环境 CPU:I5-10400 内存:16GB 系统:Win10 64位专业版,20H2 IDE:Vistual Studio 2019专业版 2. 问题描述 ...
- 状态空间表示法----野人与修道士
状态空间法的应用 修道士(Missionaries)和野人(Cannibals)问题 在河的左岸有N个传教士(M).N个野人(C)和一条船(Boat),传教士们想用这条船把所有人都运过河去,但有以下条 ...
- 修道士与野人问题——C++源代码,伪代码,详细分析
前言:这一个经典的问题,可以把问题转换成数据结构中的 图 来解决.本博客节选自我去年7月份的数据结构报告 问题描述 假设有 n 个修道士和 n 个野人准备渡河,但只有一条能容纳 c 人的小船,为了防止 ...
最新文章
- 【HDU 5402】Travelling Salesman Problem(构造)
- 实例讲解《Microsoft AJAX Library》(2):DomEvent类
- Java中对象的复制
- o型圈沟槽设计_深圳综合O型密封圈ID544.4MM*8.6MM报价-星湖蓝海科技
- IoT与大数据 如何激发数字营销最大潜能?
- java设计按月每天签到_活动攻略|新同学新签到,欢乐福利全都要!
- 利用计算机卸载,电脑使用痕迹彻底清理工具(无影无踪WYWZ)
- 信息学奥赛一本通 1078:求分数序列和 | OpenJudge NOI 1.5 32
- c# 结构体 4字节对齐_C语言程序员们常说的“内存对齐”,究竟有什么目的?
- 邮件服务器两种协议,邮件服务器协议
- python install causes ModuleNotFoundError: No module named ‘_swigfaiss‘
- 使用 windows命令和iconv.exe批量转换文件编码
- java模板导出excel_POI导出excel模板三种方式
- 图片和文本置顶显示的方法
- npm WARN deprecated bfj-node4@5.3.1: Switch to the `bfj` package for fixes and new features
- redis的几种常见客户端
- 恢复计算机管理员权限软件,帮您修复win10系统管理员权限的恢复步骤
- idea查看每行代码是谁修改的
- 【51单片机】矩阵键盘逐行扫描法仿真实验+超详细Proteus仿真和Keil操作步骤
- 中国智慧民航行业现状分析与前景规划咨询报告2022-2028年版