基于python中networkx包的传教士和野人深度优先搜索算法及可视化实现

 

 

按《人工智能原理》书上的思路进行建模。只是记录一下自己的作业,感觉之前好多用心写的代码没利用起来,发在博客整理一下,不喜勿喷。

在这个问题中,(3,3,1)为初始状态,(0,0,0) 为目标状态。全部的可能状态共有32个,如表所示, m为左岸传教士人数, c为左岸野人人数,b 左岸的船数目。

可用状态16种,考虑到任何一条从S0到达S31的路径都是该问题的解,最短路径共有4条。

 

 

 

下面是算法实现的思路:根据条件:右岸->左岸之间可去1人,右岸->左岸可去2人,同时两个可逆,遍历出符合条件的状态空间,并对这些状态空间进行图的建立,事实上比起上题中列举所有可能情景按条件删除,以上这个条件进行点的列举不会考虑绿色, 蓝色中提到的结点,当c和m的值进行改变时,具有更高的普适性。在图建立后用DFS深度优先遍历搜索算法进行搜索遍历,即最优解法。当探索最短路径时,不需要确认已经重复的节点,若确认已经重复的节点,探索的路径就不是最短路径。关于深度优先遍历搜索算法来自https://blog.csdn.net/zhangphil/article/details/121269870。下面是实验结果:

 

图5  m=3,c=3,b=2实验结果

 

 

 

图6 m=7,c=6,b=2实验结果

从图5上可发现除找出最优路径之外,还有3种同等的最优路径,图 6主要是修改m和c的实验结果拓展,某些情况无最短路径生成,已经加以限制。由于b修改会导致先前设定的条件(右岸->左岸之间可去1人,右岸->左岸可去2人,同时两个可逆)改变,这种情况加入判断语句进行控制即可,暂时没有进一步完善。

下面是代码~可以在这里下载https://github.com/DobbySquirrel/The-missionaries-and-cannibals-problem

本来想传在github上的,好慢。。。。就先写在这里啦~

# -*- coding: utf-8 -*-
"""
Created on Sat Mar 12 18:54:11 2022

@author: dobby
"""
import networkx as nx
import matplotlib.pyplot as plt
 
WALKABLE = 'walkable'
PARENT = 'parent'
VISITED = 'visited'
 
#3维网格图 
def my_graph(m,c,b):
    plt.subplots(1,1,figsize=(15,7))
    G=nx.Graph()
    #若b发生变化则以下结点的生成规则需要更改
    G.add_edges_from([
    ((x,y,0), (x,y+1,1))
    for x in range(m+1)
    for y in range(c)
    ] + [
    ((x,y,0), (x+1,y,1))
    for x in range(m)
    for y in range(c+1)
    ])#右岸->左岸之间可去1人
    G.add_edges_from([
    ((x,y,0), (x+1,y+1,1))
    for x in range(m)
    for y in range(c)
    ] + [
    ((x,y,0), (x+2,y,1))
    for x in range(m-1)
    for y in range(c+1)
    ] + [
    ((x,y,0), (x,y+2,1))
    for x in range(m+1)
    for y in range(c-1)
    ])#右岸->左岸可去2人
    pos = nx.spring_layout(G, iterations=100)
    nx.draw_networkx(G, pos=pos,
                     font_size=7,
                     font_color='white',
                     node_size=500,
                     width=1)
    START = (m,c,1)
    GOAL = (0,0,0)
 
    road_closed_nodes = dummy_nodes(G,m,c,b)

    nx.draw_networkx_nodes(
        G, pos,
        nodelist=road_closed_nodes,
        node_size=500,
        node_color="red",
        node_shape="x",
        label='x'
    )
 
    dfs(G, START, GOAL)# 基于栈实深度优先遍历搜索
    try:
        path = find_path_by_parent(G, START, GOAL)
    except:
        print("该种情况没有可行路径")
        return;
    print('path', path)
 
    nx.draw_networkx_nodes(
        G, pos,
        nodelist=path,
        node_size=400,
        node_color="green",
        node_shape='o',
    )
 
 
    path_edges = []
    for i in range(len(path)):
        if (i + 1) == len(path):
            break
        path_edges.append((path[i], path[i + 1]))
 
    print('path_edges', path_edges)
 
    G2=G.to_directed()
    nx.draw_networkx_edges(G2, pos,
                           edgelist=path_edges,
                           width=4,
                           alpha=0.5,
                           edge_color="g")
 
    plt.axis('off')
    plt.show()
 
 
# 基于栈的深度优先遍历搜索
def dfs(G, START, GOAL):
    for n in G.nodes():
        G.nodes[n]['visited'] = False#初始所有点都是未被访问的
 
    stack = []  # 用列表当作一个栈,只在栈顶操作(数组的第1个位置)
    stack.append(START)
    close_list = []
    while True:
        if len(stack) == 0:
            break
 
        print('-----')
        print('stack-', stack)
 
        visit_node = stack[0]
        G.nodes[visit_node]['visited'] = True
        print('访问', visit_node)
 
        if visit_node == GOAL:
            break
 
        close_list.append(visit_node)#已访问的节点
 
        count = 0
        neighbors = nx.neighbors(G, visit_node)#寻找visit_node周围的结点 
        for node in neighbors:#依次遍历visit_node周围的节点
            visited = G.nodes[node][VISITED]
            try:
                walkable = G.nodes[node][WALKABLE]#如果存在之前设定的WALKABLE则为阻碍点
            except:
                walkable = True
 
            if (visited) or (node in stack) or (node in close_list) or (not walkable):
                continue
 #如果结点被访问,或者在将要访问的stack中,或者在已被记录的最短路径close_list中,或者是阻碍点 则跳过
    
            G.nodes[node][PARENT] = visit_node#设定该找到的节点node的父节点为visit_node,改变G
            stack.append(node)#将node压入stack
            count = count + 1#计数
 
        if count == 0:#该节点下没有节点被压入
            print(visit_node, '尽头')
            del (stack[0])
            print('弹出', visit_node)
 
        print('stack--', stack)#显示进入下次循环的stack
 
    return stack
 
 
def find_path_by_parent(G, START, GOAL):
    t = GOAL
    path = [t]
    is_find = False
    while not is_find:
        for n in G.nodes(data=True):#G.nodes第一个是结点的数据,第二个是字典存储的结点属性
            if n[0] == t:
                parent = n[1][PARENT]#找Node中parent属性存储的信息
                path.append(parent)
 
                if parent == START:#找到开始结点
                    is_find = True
                    break
 
                t = parent
 
    path.reverse()#从GOAL->START需要翻转
    return path
  
def dummy_nodes(G,m,c,b):

    list1=[];
    #list1.append((m,c,0))#去右岸时,船未携带人 不需要考虑本不在生成点中
    #list1.append((0,0,1))#左岸无人却有船  不需要考虑本不在生成点中
    for i in range(m+1):
        for j in range(c+1):
            if(i<j and i!=0):#左岸野人会袭击传教士的情况
                p=(i,j,0)
                q=(i,j,1)
                list1.append(p)
                list1.append(q)
        
            if(m-i<c-j and m-i!=0):#右岸野人会袭击传教士的情况
                p=(i,j,0)
                q=(i,j,1)
                list1.append(p)
                list1.append(q)
    for i in list1:
        G.nodes[i][WALKABLE] = False
    
    return list1
  
if __name__ == '__main__':
    m=3#自定义修道士数
    c=3#自定义野人数
    b=2
    my_graph(m,c,b)

 

posted @ 2022-03-16 22:41  白色不耐脏鸭  阅读(504)  评论(0)    收藏  举报