A*算法求解迷宫寻路问题资源链接如右:A*算法求解迷宫寻路问题
求点赞关注
如果觉得这篇文章对你有帮助请点赞收藏加关注啊,真的很谢谢大家!大家可以进入我的CSDN主页查看其它文章,都是我在进行课后题目与课程设计时遇到的一些问题,如果你正在学习人工智能,一定会有所收获,并且可以在我的GitHub仓库主页下载相关代码,如无法进入GitHub仓库主页也可以进入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×(∣nx−endx∣+∣ny−endy∣),代码如下:
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)=(nx−endx)2+(ny−endy)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)→(20,20)的路径如图2,(9,5)→(18,20)的路径如图3。
两次寻路过程的性能分析如下表1。
路径 | (1,1)→(20,20) | (9,5)→(18,20) |
---|---|---|
扩展节点数 | 100 | 73 |
生成节点数 | 71 | 57 |
运行时间 | 0.00199795 | 0.00200462 |
由上述图表可知路径延长后运行时间会相对变长,扩展节点及生成节点也会相应增多,且两次寻找的路程有重合的部分,所以寻找到的路径为较优路径。
分析不同启发式函数
采用地图2,启发式函数
h
1
h_1
h1,
h
2
h_2
h2进行分析,设置起点为(1,1),终点为(20,20),两次寻路过程的路径,扩展节点数、生成节点数和运行时间分别如下图4,图5。
由图表可知,两种启发式函数所用时间相近,但 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。
汇总上述性能报告如下表2。
地图 | 启发式函数 | 扩展节点数 | 生成节点数 | 运行时间 |
---|---|---|---|---|
地图1 | h 2 h_2 h2 | 14 | 13 | 0.00100136 |
地图2 | h 1 h_1 h1 | 100 | 71 | 0.00199795 |
地图2 | h 2 h_2 h2 | 97 | 71 | 0.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='')