前言
关于A*算法的实现是很早之前的一次开发中的成果,并做了一些改进。当然,在这里就不记录改进部分了,因为其中还有一些争议。这里仅是对A*算法的理解和使用Python实现。
参考链接
之所以放在前面,是因为这些链接的参考价值特别高,如果希望获得更多的了解,可以通过以下链接进行学习。
英文网站
A* Pathfinding for Beginners - Artificial Intelligence - Tutorials - GameDev.net
redblobgames(红色斑点游戏)
中文网站
csdn:A星算法详解(个人认为最详细,最通俗易懂的一个版本)|模块 A*算法总结 非常有参考价值
A星算法详解(个人认为最详细,最通俗易懂的一个版本)_Colin丶-优快云博客_a星算法
知乎:路线规划之A*算法
时间线
2021.03.25 优化
2021.11.03权重优化
定义
(百度百科)A*(A-Star)算法是一种静态路网中求解最短路径最有效的直接搜索方法,也是解决许多搜索问题的有效算法。算法中的距离估算值与实际值越接近,最终搜索速度越快
一个真正的A*算法必须包含以下对象:开启列表、关闭列表、G、H
分析
估价函数F(n)=G(n)+H(n)
-
G(n)
G(n)表示的是从起始节点到当前节点的距离代价。
在A*算法中,这是一个确切但可变的数值
可变是因为从起始节点到当前节点的不同移动方案,其距离代价也是不同的
确切是一旦移动方案的确定,其距离代价也确定了
例如以下的
如果从节点A出发,那么选择方案1的话,G(n)=30,这是确切的
如果有更好的移动方案,如这里的方案3的话,则G(n)=/2*10,这是可变的
(假设一个正方形的大小为10)
-
H(n)
H(n)表示的是从当前节点到目标节点的估计代价,也就是说只是一个估计值。
也是A*算法的启发函数
有3种处理方式:
方式1:如果从当前点出发,可以向上下左右四个方向移动,则使用曼哈顿距离
方式2:如果从当前点出发,可以向上下左右四个方向移动外,还能斜方向运行,共八个方向,则使用对角距离
方式3:允许向任何方向移动,则使用欧几里得距离
-
补充
曼哈顿距离、对角距离、欧氏距离
思考:3种距离对A*算法的影响
曼哈顿距离可以说是最简单粗暴的计算距离的方法
其实对角距离是将欧氏距离的处理方法局部化,局限于当前节点的当前移动。
而欧氏距离是对角距离的整体化。
思考:G(n)和H(n)的区别
1.G(n)是个可变但确切的实际值,H(n)是个确切的估计值
一旦当前点的确定,H(n)将是一个定值.
而G(n)会随着移动方案的改变而改变
2.G(n)是无法通过欧氏距离计算的。
因为每一个静态路网中,如果不存在不可达的点,那么A*算法将没有存在的意义。
每一次从起点到达终点,只需要从起点出发,向终点方向移动即可。
而当存在不可达点,那么如果使用欧氏距离计算G(n),如果起点到当前点的欧氏距离线上有一个障碍物,
则实际的移动代价将会大于G(n)
而H(n)可以使用3种距离计算方式
步骤
详细步骤
创建开启列表、关闭列表
将起始点加入开放列表中
弹出开启列表的第一个节点,将该元素作为当前节点
while(当前节点不为终点时):
获取并处理当前节点的周围可通过的节点,
为这些节点设置G值、H值,
添加当前节点为这些可通过节点的父节点
遍历当前节点的周围节点
if(该节点可通过且不在关闭列表):
if(该节点在开启列表):
根据 估价函数F(n)进行比较
if(开启列表的节点的估价函数大于该节点的估价函数):
将开启列表的节点进行覆盖
else:
将该节点加入开启列表
将当前节点添加到关闭列表中
对开启列表进行排序
当前节点<-获得开启列表的第一个元素
创建一个路径结果集
while(当前节点的父节点不为空):
获得当前节点的索引,存入路径结果集中
当前节点<-当前节点的父节点
返回路径结果集
结束
细节处理
-
关键字概念
父节点、邻接节点
-
对象
地图(地图数据,起点,终点)
节点(包含H、G,父节点)
开启列表
关闭列表
-
主要处理
1.获得当前节点的可用的邻居节点
2.将邻接节点插入开启列表中
3.对开启列表进行排序处理(根据G(n)值)
4.查找一个节点是否在开启列表中
注:可以将2与3进行整合,在插入时同时实现排序处理
代码
-
地图对象Map
import math
'''
对象Map,主要有地图数据、起点和终点
'''
class Map(object):
def __init__(self,mapdata,startx,starty,endx,endy):
self.data = mapdata
self.startx = startx
self.starty = starty
self.endx = endx
self.endy = endy
-
节点Node
'''
Node.py主要是描述对象Node
'''
class Node(object):
'''
初始化节点信息
'''
def __init__(self,x,y,g,h,father):
self.x = x
self.y = y
self.g = g
self.h = h
self.father = father
'''
处理边界和障碍点
'''
def getNeighbor(self,mapdata,endx,endy):
x = self.x
y = self.y
result = []
#先判断是否在上下边界
#if(x!=0 or x!=len(mapdata)-1):
#上
#Node(x,y,g,h,father)
if(x!=0 and mapdata[x-1][y]!=0):
upNode = Node(x-1,y,self.g+10,(abs(x-1-endx)+abs(y-endy))*10,self)
result.append(upNode)
#下
if(x!=len(mapdata)-1 and mapdata[x+1][y]!=0):
downNode = Node(x+1,y,self.g+10,(abs(x+1-endx)+abs(y-endy))*10,self)
result.append(downNode)
#左
if(y!=0 and mapdata[x][y-1]!=0):
leftNode = Node(x,y-1,self.g+10,(abs(x-endx)+abs(y-1-endy))*10,self)
result.append(leftNode)
#右
if(y!=len(mapdata[0])-1 and mapdata[x][y+1]!=0):
rightNode = Node(x,y+1,self.g+10,(abs(x-endx)+abs(y+1-endy))*10,self)
result.append(rightNode)
#西北 14
if(x!=0 and y!=0 and mapdata[x-1][y-1]!=0 ):
wnNode = Node(x-1,y-1,self.g+14,(abs(x-1-endx)+abs(y-1-endy))*10,self)
result.append(wnNode)
#东北
if(x!=0 and y!=len(mapdata[0])-1 and mapdata[x-1][y+1]!=0 ):
enNode = Node(x-1,y+1,self.g+14,(abs(x-1-endx)+abs(y+1-endy))*10,self)
result.append(enNode)
#西南
if(x!=len(mapdata)-1 and y!=0 and mapdata[x+1][y-1]!=0 ):
wsNode = Node(x+1,y-1,self.g+14,(abs(x+1-endx)+abs(y-1-endy))*10,self)
result.append(wsNode)
#东南
if(x!=len(mapdata)-1 and y!=len(mapdata[0])-1 and mapdata[x+1][y+1]!=0 ):
esNode = Node(x+1,y+1,self.g+14,(abs(x+1-endx)+abs(y+1-endy))*10,self)
result.append(esNode)
# #如果节点在关闭节点 则不进行处理
# finaResult = []
# for i in result:
# if(i not in lockList):
# finaResult.append(i)
# result = finaResult
return result
def hasNode(self,worklist):
for i in worklist:
if(i.x==self.x and i.y ==self.y):
return True
return False
#在存在的前提下
def changeG(self,worklist):
for i in worklist:
if(i.x==self.x and i.y ==self.y):
if(i.g>self.g):
i.g = self.g
-
核心逻辑astar
from Node import Node
def getKeyforSort(element:Node):
return element.g #element#不应该+element.h,否则会穿墙
def astar(workMap):
startx,starty = workMap.startx,workMap.starty
endx,endy = workMap.endx,workMap.endy
startNode = Node(startx, starty, 0, 0, None)
openList = []
lockList = []
lockList.append(startNode)
currNode = startNode
while((endx,endy) != (currNode.x,currNode.y)):
workList = currNode.getNeighbor(workMap.data,endx,endy)
for i in workList:
if (i not in lockList):
if(i.hasNode(openList)):
i.changeG(openList)
else:
openList.append(i)
openList.sort(key=getKeyforSort)#关键步骤
currNode = openList.pop(0)
lockList.append(currNode)
result = []
while(currNode.father!=None):
result.append((currNode.x,currNode.y))
currNode = currNode.father
result.append((currNode.x,currNode.y))
return result
-
运行run
from astar import astar
from Map import Map
mymap = [
[1,1,1,0,1,0,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1],
[1,1,1,0,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],
[1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1],
[1,1,1,0,1,0,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1],
[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1],
[1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1],
[1,1,1,0,1,0,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1],
[1,1,1,0,1,0,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1],
[1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1]
]
map = Map(mymap,0,1,5,5)
result = astar(map)
result.reverse()
print(result)
缺陷
A*算法相比于同系列的其它两个算法,有它的优点,但也有其缺点。在一些情况下会出现。
1.当目标不可达时,程序会一直执行。
(这是因为其逻辑,当目标点被处理时,才会停止,否则,一直执行。
所以,当目标不可达时,程序会一直执行,无法跳出。)
2.当起点到终点有障碍物时,A*算法得到的可能不是最佳路径
总结
AStar算法广泛运用于游戏寻路模块中,初看时觉得很神奇,但是经过学习后,其实会发现其实现逻辑并不复杂。
也能感受到其中的魔力。
补充
- 有位读者说怎么运行,可以像我这样保存文件,最后直接运行即可。