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

本文通过Python实现广度优先搜索(BFS)算法来解决传教士与野人问题。首先介绍了问题背景和搜索算法的基本元素,然后详细阐述了BFS算法的思路,包括状态、动作、转换模型和目标测试。算法通过递归建图和搜索路径,最终输出所有可能的解决方案。代码中包含了输入处理、状态判断、建图过程和路径搜索等功能。
摘要由CSDN通过智能技术生成

写在前面

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

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

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

……

……

……

算法思路

本文将使用递归实现广度优先搜索算法(BFS),使用graph = {}来记录图,键(key)为父节点,值(value)为子节点,一个父节点可以带着很多子节点,一个子节点也可以在很多的父节点的后面,因此非常符合Python字典的特点,使用起来非常方便。算法的思路如下:

输入

用户输入信息,其中包括N和K的值,用以后续的计算。随即生成初始状态[N, K, 0, 0, 1]。

生成动作

生成可行的动作[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
更新日期:2020.11.03
文件名:CrossRiverBFS.py
"""
# 用以测试运行时间
import time
"""globals"""
n = 0           # 传教士与野人各自的人数N
q = []          # 作为队列
actions = []    # 动作,即变化的方式,表示方式[M, C](传教士、野人)
path = []       # 递归查找的单次路径
paths = []      # 存放多个路径
checkList = []  # 已经遍历过的点
graph = {}      # 图


# 判断状态是否满足条件,同时建图
def ok(state_b, state):
    # 判断是否都不小于0
    if 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():
        if tuple(state) not in graph[tuple(state_b)]:
            graph[tuple(state_b)].append(tuple(state))
            pass
    else:
        graph[tuple(state_b)] = [tuple(state)]

    # 已经遍历过,不需要继续遍历
    if state in checkList:
        return False

    return True


def mapping():
    # 队列非空
    while len(q) > 0:
        # 出队
        state_b = q.pop(0)
        checkList.append(state_b)

        # 到达目标状态
        if state_b[0] == 0 and state_b[1] == 0:
            continue

        # 执行动作
        state_n = [0]*5
        for 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 ok(state_b, temp):
                # 入队
                if temp not in q:
                    q.append(temp)
                    pass
                pass
            pass
        pass
    pass


# 深度搜索寻找路径
def find_path(state):
    global n

    # 走到重复的状态
    if state in path:
        path.append(state)
        return

    # 到达终点状态,记录路径
    if state == (0, 0, n, n, -1):
        path.append(state)
        paths.append(path[:])
        return

    path.append(state)

    # 逐个探索
    for i in range(len(graph[state])):
        find_path(graph[state][i])
        path.pop()
        pass

    pass


def main():
    global n

    # 输入
    n = int(input("输入人数N:"))
    k = int(input("输入载客量K:"))

    # 初始状态
    state = [n, n, 0, 0, 1]
    q.append(state)

    # 生成动作
    # i:移动传教士和野人之和,从1到k
    for i in range(1, k + 1):
        # j:传教士的数目,从0到i
        for j in range(i + 1):
            # 如果满足传教士不少于野人或传教士为0,动作有效
            if (j >= i - j) or (j == 0):
                actions.append([j, i - j])
                pass
            pass
        pass
    # 生成完毕

    # 记录开始时间
    start = time.perf_counter()
    # 建图
    mapping()

    # 计算总时长
    total = time.perf_counter() - start
    print(total)

    # 搜索路径
    find_path(tuple(state))

    # 路径条数
    num = 0
    # 输出路径
    for p in paths:
        num += 1
        print("第%d条路径:" % num)
        str1 = "{:^6}{:^6}{:^6}{:^6}{:^6}"
        print(str1.format("ML", "CL", "MR", "CR", "B"))
        for i in p:
            print(str1.format(i[0], i[1], i[2], i[3], i[4]))
            pass
        pass

    # 结束
    print("总共有%d条路径" % num)
    pass


if __name__ == '__main__':
    try:
        main()
    except Exception as e:
        print(e)
        pass
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Guo_Zhanyu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值