当前位置: 代码迷 >> 综合 >> 传教士与野人问题广度优先搜索算法(BFS)-Python实现
  详细解决方案

传教士与野人问题广度优先搜索算法(BFS)-Python实现

热度:81   发布时间:2024-03-10 00:41:41.0
  • 写在前面

书接上文:传教士与野人问题深度优先搜索算法(DFS)-Python实现

在研究并实现了深度优先搜索算法(DFS)以后,这篇文章将研究使用广度优先算法(BFS)解决传教士与野人问题。

关于搜索问题及传教士与野人问题的要素与抽象,已经在前文有详细解释,在这篇广度优先算法的文章里,使用的状态(State)、动作(Action)、转换模型、目标测试(Goal test)和路径代价(Cost)都与DFS相同。在本文就不再赘述,可以点开上文链接,阅读"算法思路"版块之前所有的内容,以便更好地理解本文。

……

……

……

  • 算法思路

本文将使用递归实现广度优先搜索算法(BFS),算法的思路如下:

  • 一些变量的说明

与DFS不同,该算法的思路是先构建一张图,然后再通过图输出路径。使用graph = {}来记录图,键(key)为父节点,值(value)为子节点,一个父节点可以带着很多子节点,一个子节点也可以在很多的父节点的后面,因此非常符合Python字典的特点,使用起来非常方便。

  • 输入

用户输入信息,其中包括N和K的值,用以后续的计算。

  • 生成动作

生成可行的动作[m, c],其中要求m + c ≤ K && (m ≥ c || m == 0)。

对m和c从0到K开始遍历,将所有可行的动作加入到列表actions当中:[[m1, c1], [m2, c2], ...]

  • 开始搜索建图

初始化一个队列q(FIFO, first in first out)用来存储需要遍历的节点。

首先将起点[n, n, 0, 0, 1]入队,开始循环直到队空,也就是没有需要遍历的节点了,图已经建立完毕。

在循环内,先出队一个状态,如果这个状态是终点状态的话,就继续出下一个状态。其他情况下就是在到终点的路上,需要对该状态执行其中一个动作,然后判断是否符合条件。

条件为:

  1. 状态中的各个人数都大于0;
  2. 满足不被吃的条件。

条件如果符合,那么将该节点加入到graph的字典中,key值跟value值就是当前节点跟执行完动作产生的节点。

在符合上面条件的前提下,如果该节点没有遍历过,那么将该节点再入队,等待遍历。

然后就是再执行不同的动作,再寻找能到的节点,重复上面的判断及入图、入队操作。

直到该节点所有动作执行完毕,本次循环结束,就可以再次出队下一个节点。

等队空时,循环结束。

  • 输出路径

输出路径使用的方法是递归的方法。从初始状态开始,在graph中进行深度优先搜索。

以初始状态为key值,遍历每个value,将value继续作为key值,然后同样遍历其value值。在此过程中记录下路径。

找到终点后,将该路径输出,然后return上一层,继续找。直到最后搜索完毕return。

  • 结束

  • 代码(Python)

"""
作者:Zhanyu_Guo
日期:2020.10.25
文件名:CrossRiverBFS.py
"""
import time
"""globals"""
n = 0  # 人数,因为要用到别的函数里,直接作为全局变量
num = 0  # 路径的个数
start = 0
q = []  # 作为队列
actions = []  # 动作,即变化的方式,表示方式[M, C](传教士、野人)
path = []  # 路径
checkList = []  # 已经遍历过的点
graph = {}  # 图def judge(state_b, state):# 判断是否都不小于0if state[0] < 0 or state[1] < 0 or state[2] < 0 or state[3] < 0:return False# 判断是否都满足不被吃的条件if (state[0] < state[1] and state[0] != 0) or (state[2] < state[3] and state[2] != 0):return False# 满足上述条件为有效点,加入到图中if tuple(state_b) in graph.keys():graph[tuple(state_b)].append(tuple(state))else:graph[tuple(state_b)] = [tuple(state)]# 已经遍历过,不需要继续遍历if state in checkList:return Falsereturn Truedef handler():global num, start# 队列非空while len(q) > 0:# 出队state_b = q.pop(0)checkList.append(state_b)# 到达目标状态if state_b[0] == 0 and state_b[1] == 0:end = time.perf_counter() - startprint(end)continue# 执行动作state_n = [0]*5for action in actions:state_n[0] = state_b[0] - action[0] * state_b[4]state_n[1] = state_b[1] - action[1] * state_b[4]state_n[2] = state_b[2] + action[0] * state_b[4]state_n[3] = state_b[3] + action[1] * state_b[4]state_n[4] = -state_b[4]temp = state_n[:]# 判断是否符合条件if judge(state_b, temp):# 入队if temp not in q:q.append(temp)def show(state):global n, num# 判断是否走过重复的点if state in path:# 加入列表,因为递归结束后会弹出path.append(state)return# 到达目标状态if state == (0, 0, n, n, -1):path.append(state)num += 1print("第%d条路径:" % num)# 显示路径str1 = "{:^6}{:^6}{:^6}{:^6}{:^6}"print(str1.format("ML", "CL", "MR", "CR", "B"))for i in path:print(str1.format(i[0], i[1], i[2], i[3], i[4]))return# 符合条件,加入路径path.append(state)# 寻找该点的子节点for i in range(len(graph[state])):show(graph[state][i])path.pop()def main():global n, start# 输入n = int(input("输入人数N:"))k = int(input("输入载客量K:"))# 初始状态state = [n, n, 0, 0, 1]q.append(state)# 生成动作# i:移动传教士和野人之和,从1到kfor i in range(1, k + 1):# j:传教士的数目,从0到ifor j in range(i + 1):# 如果满足传教士不少于野人或传教士为0,动作有效if (j >= i - j) or (j == 0):actions.append([j, i - j])# 生成完毕# 非递归start = time.perf_counter()handler()# 递归显示路径show((n, n, 0, 0, 1))print("总共有%d条路径" % num)if __name__ == '__main__':try:main()except Exception as e:print(e)

 

  相关解决方案