A*算法的实现(Python)

前言

关于A*算法的实现是很早之前的一次开发中的成果,并做了一些改进。当然,在这里就不记录改进部分了,因为其中还有一些争议。这里仅是对A*算法的理解和使用Python实现。


参考链接

之所以放在前面,是因为这些链接的参考价值特别高,如果希望获得更多的了解,可以通过以下链接进行学习。

英文网站

A* Pathfinding for Beginners - Artificial Intelligence - Tutorials - GameDev.net

redblobgames(红色斑点游戏)

Red Blob Games

中文网站

csdn:A星算法详解(个人认为最详细,最通俗易懂的一个版本)|模块 A*算法总结 非常有参考价值

A星算法详解(个人认为最详细,最通俗易懂的一个版本)_Colin丶-CSDN博客_a星算法

知乎:路线规划之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算法广泛运用于游戏寻路模块中,初看时觉得很神奇,但是经过学习后,其实会发现其实现逻辑并不复杂。

也能感受到其中的魔力。

补充

  • 有位读者说怎么运行,可以像我这样保存文件,最后直接运行即可。

  • 62
    点赞
  • 298
    收藏
    觉得还不错? 一键收藏
  • 28
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 28
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值