A*算法求解迷宫寻路问题(启发式算法)

A*算法求解迷宫寻路问题资源链接如右:A*算法求解迷宫寻路问题

求点赞关注

如果觉得这篇文章对你有帮助请点赞收藏加关注啊,真的很谢谢大家!大家可以进入我的CSDN主页查看其它文章,都是我在进行课后题目与课程设计时遇到的一些问题,如果你正在学习人工智能,一定会有所收获,并且可以在我的GitHub仓库主页下载相关代码,如无法进入GitHub仓库主页也可以进入Gitee进行查看,后续我也会根据需求不断完善。

lazyn的CSDN_blog_code(GitHub)
lazyn的CSDN_blog_code(Gitee)

同时如果想要系统化的学习人工智能,可以进入下面的网站进行学习

通俗易懂,风趣幽默的人工智能学习网站-床长人工智能教程

作为人工智能专业的学生,我认为该网站的课程设置足够专业与完整,由浅入深,基本涵盖了当前人工智能的热门领域并且在不断完善,目录简洁明了,大家可以对照目录进行查漏补缺,作为读者,我发现课程内容通俗易懂,风趣幽默,可以激发大家的学习兴趣。

改进

2023-03-24

  • 删除 op 函数,通过 sorted 函数对 Open 表根据代价从大到小排序,每次取 Open 表最后一个值作为扩展节点
  • 每次获得 Open 表时判断节点是否在 Opens 表中,即是否已扩展,避免重复判断
  • 新增变量 crossings 用于存储未走的分叉节点,实现寻路回溯

修改后代码可以实现评论区迷宫的寻路,结果如下图所示。
修改后代码结果

感谢评论区 weeknight, m0_57493007, 子夜ザ“, 格”式化 等人的指正。

实验内容

在一个n×m的迷宫里,入口坐标和出口坐标分别为(startx,starty)和(endx,eny),每一个坐标点有两种可能:0或1,其中0表示该位置允许通过,1表示该位置不允许通过。以寻路问题为例实现A*算法的求解程序,设计两种不同的估价函数。

设置相关数据

设置两种地图

根据题意,用矩阵设置两个地图。
地图1:设置5行5列的迷宫,代码如下:

a = np.mat([[0, 0, 0, 0, 0], 
            [1, 0, 1, 0, 1], 
            [0, 0, 1, 1, 1], 
            [0, 1, 0, 0, 0], 
            [0, 0, 0, 1, 0]])

地图2:设置20行20列的迷宫,代码如下

a = np.mat([[0, 1, 0, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1], 
            [0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1], 
            [0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1],
            [0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 1],
            [1, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1],
            [0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0, 1],
            [0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1],
            [1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1],
            [0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1],
            [0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0],
            [0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1],
            [0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1],
            [0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0],
            [0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1],
            [1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
            [0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
            [0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
            [0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
            [1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0],
            [0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 0]])

设置两种启发式函数

定义估价函数
f ( n ) = g ( n ) + h ( n ) f(n)=g(n)+h(n) f(n)=g(n)+h(n)
式中, g ( n ) g(n) g(n)为起点到 n n n状态的最短路径代价的估计值, h ( n ) h(n) h(n) n n n状态到目的状态的最短路径代价的估计值。
g ( n ) g(n) g(n)为起点到 n n n状态的曼哈顿距离,代码如下:

def gs(i, j):
    return abs(i - startx) + abs(j - starty)

定义两种启发式函数。
启发式函数 h 1 ( n ) h_1 (n) h1(n) h 1 ( n ) = 10 × ( ∣ n x − e n d x ∣ + ∣ n y − e n d y ∣ ) h_1 (n)=10×(|n_x-endx|+|n_y-endy|) h1(n)=10×(nxendx+nyendy),代码如下:

def h1(i, j):
    return 10*(abs(i - endx) + abs(j - endy))

启发式函数 h 2 ( n ) h_2 (n) h2(n) h 2 ( n ) = ( n x − e n d x ) 2 + ( n y − e n d y ) 2 h_2 (n)=(n_x-endx)^2+(n_y-endy)^2 h2(n)=(nxendx)2+(nyendy)2,代码如下:

def h2(i, j):
    return pow(i - endx, 2) + pow(j - endy, 2)

性能分析

分析不同起点终点

采用地图2,具体如下图1,启发式函数 h 1 h_1 h1进行分析,分别设置起点为(1,1)和(9,5),终点为(20,20)、(18,20)。
图1 地图

(1,1)→(20,20)的路径如图2,(9,5)→(18,20)的路径如图3。
图2 路径1
图3 路径2
两次寻路过程的性能分析如下表1。

路径(1,1)→(20,20)(9,5)→(18,20)
扩展节点数10073
生成节点数7157
运行时间0.001997950.00200462

由上述图表可知路径延长后运行时间会相对变长,扩展节点及生成节点也会相应增多,且两次寻找的路程有重合的部分,所以寻找到的路径为较优路径。

分析不同启发式函数

采用地图2,启发式函数 h 1 h_1 h1 h 2 h_2 h2进行分析,设置起点为(1,1),终点为(20,20),两次寻路过程的路径,扩展节点数、生成节点数和运行时间分别如下图4,图5。
图4 启发式函数h1
图5 启发式函数h2

由图表可知,两种启发式函数所用时间相近,但 h 2 h_2 h2相比 h 1 h_1 h1所扩展的节点数会减少,仔细分析可以发现启发式函数 h 1 h_1 h1在路径108位置首先寻找了另一条岔路,从而相比启发式函数 h 2 h_2 h2多扩展了3个节点。

分析不同地图

上述过程一直采用地图2进行实验,为验证程序的普适性,现采用地图1进行实验。经过分析,选择上述较优的启发式函数 h 2 h_2 h2进行分析。寻路问题初始节点为(1,1),目标节点为(5,5),寻路路径如图6。
图6 地图1

汇总上述性能报告如下表2。

地图启发式函数扩展节点数生成节点数运行时间
地图1 h 2 h_2 h214130.00100136
地图2 h 1 h_1 h1100710.00199795
地图2 h 2 h_2 h297710.00251245

总结

A*算法在地图寻路问题中具有很好的优势,相比宽度优先搜索,效率更高,所耗时间更少,相比深度优先搜索,可以解决其不能找到最优解的不足,具有较高的应用价值。该算法的重点及难点就是启发式函数的选择,通过上述实验,可以发现启发式函数 h 2 h_2 h2相比 h 1 h_1 h1效果更好,但仍存在一些问题,需要继续找寻更优的启发式函数。

附录代码

主程序代码如下,其中用到的方法和地图在上述代码中已定义,故不再给出。设置不同的起点与终点需相应的更改startx, starty和endx, endy的值。

print("地图为:(1表示墙,0表示路)")
for l in range(len(a)):
    for m in range(a[0].size):
        print(a[l, m], end=' ')
    print('')
print('')

startx, starty = 1, 1
endx, endy = 5, 5
if a[startx - 1, starty - 1] == 1:
    print("起点%s位置为墙,最短路径寻找失败!" % ([startx, starty]))
else:
    # 存储已扩展结点,即所有寻找的节点
    Close = [[startx, starty]]
    # 存储已生成节点,即最终走过的节点
    Opens = [[startx, starty]]
    # 存储未走的分叉节点
    crossings = []
    # 设置在原地图行走的路径值
    road = 100
    start = time.time()
    rows, cols = a.shape
    while True:
        # 判断最后走过的节点是否为终点
        if Close[-1] != [endx, endy]:
            Open = []
            # 减1得到数组下标值
            i, j = Close[-1][0] - 1, Close[-1][1] - 1
            # 对当前节点上下左右四个节点进行判断
            for ni, nj in [(i - 1, j), (i + 1, j), (i, j - 1), (i, j + 1)]:
                if [ni + 1, nj + 1] not in Opens and 0 <= ni < rows and 0 <= nj < cols and a[ni, nj] == 0:
                    Open.append([ni + 1, nj + 1])
            # 将已走过的节点值修改为路径值,并将路径值加1
            a[i, j] = road
            road = road + 1
            if Open:
                # 对所有扩展到的节点进行排序,reverse=True 结果从大到小,即尾部存储代价最小的节点
                Open = sorted(Open, key=lambda x: gs(x[0], x[1]) + h2(x[0], x[1]), reverse=True)
                Opens.extend(Open)
                # 将代价最小的节点存储到 Close 表
                Close.append(Open.pop())
                # 如果 pop 后 Open 表不为空,说明存在岔路,将岔路存储到 crossings 中
                if Open:
                    crossings.extend(Open)
            # 判断是否存在未走过的分叉节点
            elif crossings:
                next_way = crossings.pop()
                # 实现路径回退,循环条件为回退节点与分叉节点不相邻
                while sum((np.array(Close[-1]) - np.array(next_way)) ** 2) != 1:
                    # 当一条路径寻找失败后,是否将该路径岔路后的部分恢复为原地图
                    # i, j = Close[-1]
                    # a[i-1, j-1] = 0
                    # 每次 while 循环路径值 road 减1,并弹出 Close 表中的节点
                    road -= 1
                    Close.pop()
                # 将 crossings 中最后一个节点添加到 Close 表
                Close.append(next_way)
            else:
                print("最短路径寻找失败,失败位置为:%s,路径为:" % Close[-1])
                break
        else:
            a[endx - 1, endy - 1] = road
            print("最短路径寻找成功,路径为:")
            break
    for r in range(rows):
        for c in range(cols):
            # 0表示format中第一个元素,>表示右对齐输出,4表示占四个字符
            print("{0: >4}".format(a[r, c]), end='')
        print('')
    end = time.time()
    print("\n扩展节点数为:%d, 生成节点数为:%d, 用时为 %.8f" % (len(Opens), len(Close), float(end - start)))

    print('Close表为:%s' % Close)
    print("点的移动轨迹为:")
    for k in range(len(Close)):
        print(Close[k], end='')
        if k < len(Close) - 1:
            print("-->", end='')

评论 66
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

lazyn

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

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

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

打赏作者

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

抵扣说明:

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

余额充值