前言
關於A*算法的實現是很早之前的一次開發中的成果,並做了一些改進。當然,在這裏就不記錄改進部分了,因爲其中還有一些爭議。這裏僅是對A*算法的理解和使用Python實現。
參考鏈接
之所以放在前面,是因爲這些鏈接的參考價值特別高,如果希望獲得更多的瞭解,可以通過以下鏈接進行學習。
英文網站
https://www.gamedev.net/reference/articles/article2003.asp
redblobgames(紅色斑點遊戲)
中文網站
csdn:A星算法詳解(個人認爲最詳細,最通俗易懂的一個版本)|模塊 A*算法總結 非常有參考價值
https://blog.csdn.net/hitwhylz/article/details/23089415
知乎:路線規劃之A*算法
https://zhuanlan.zhihu.com/p/54510444
定義
(百度百科)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_Obj
import math
class Map_Obj():
def __init__(self,mapdata,startx,starty,endx,endy):
self.data = mapdata
self.startx = startx
self.starty = starty
self.endx = endx
self.endy = endy
#縮小數據節點集
def getBetterMap(self):
#爲起點和終點設置標誌
self.data[self.startx][self.starty] = -1
self.data[self.endx][self.endy] = 2
#獲取範圍內的半徑
r = math.sqrt((self.endx-self.startx)*(self.endx-self.startx)+(self.endy-self.starty)*(self.endy-self.starty))
r = math.ceil(r)
#取左邊的數
mapleft = self.starty-r if self.starty-r>0 else 0
# #取右邊
mapright = (len(self.data[0])-1) if self.starty+r>len(self.data[0])-1 else self.starty+r
# #取上邊
mapup = self.startx-r if self.startx-r>0 else 0
# #取下邊
mapdown = (len(self.data)-1) if self.startx+r>(len(self.data)-1) else self.startx+r
#判斷終點在起點的相對位置
if(self.endx>self.startx):#終點在下邊
mapup = self.startx
else:#終點在一條線上或者上邊
mapdown = self.startx
newMap = Map_Obj([],None,None,None,None)
for x in range(mapup,mapdown+1):
newMap.data.append(self.data[x][mapleft:mapright+1])
#取消標記,獲取新的起點和終點
for x in range(len(newMap.data)):
for y in range(len(newMap.data[0])):
if newMap.data[x][y] == -1:
newMap.startx,newMap.starty = x,y
newMap.data[x][y] = 1
if newMap.data[x][y] == 2:
newMap.endx,newMap.endy = x,y
newMap.data[x][y] = 1
return newMap
-
節點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,mymap,endx,endy):
x = self.x
y = self.y
result = []
#先判斷是否在上下邊界
#if(x!=0 or x!=len(mymap)-1):
#上
#Node(x,y,g,h,father)
if(x!=0 and mymap[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(mymap)-1 and mymap[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 mymap[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(mymap[0])-1 and mymap[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 mymap[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(mymap[0])-1 and mymap[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(mymap)-1 and y!=0 and mymap[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(mymap)-1 and y!=len(mymap[0])-1 and mymap[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
if __name__ == "__main__":
node = Node(1,1,None,None,None)
print(node.x)
-
核心邏輯MyAstar
def getKeyforSort(element:Node):
return element.g#element#
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)#關鍵步驟
#openList = heapq.nsmallest(len(openList), openList, key=lambda s: s.g)
currNode = openList.pop(0)
# print(currNode.x,currNode.y)
lockList.append(currNode)
# print(currNode.x,currNode.y)
result = []
while(currNode.father!=None):
result.append((currNode.x,currNode.y))
currNode = currNode.father
result.append((currNode.x,currNode.y))
return result
-
運行結果
from MyAStar import astar
from Map_Obj import Map_Obj
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_Obj(mymap,0,1,5,5)
result = astar(map)
result.reverse()
print(result)
#輸出結果:[(0, 1), (1, 2), (2, 3), (3, 4), (4, 4), (5, 5)]
缺陷
A*算法相比於同系列的其它兩個算法,有它的優點,但也有其缺點。在一些情況下會出現。
1.當目標不可達時,程序會一直執行。
(這是因爲其邏輯,當目標點被處理時,纔會停止,否則,一直執行。
所以,當目標不可達時,程序會一直執行,無法跳出。)
2.當起點到終點有障礙物時,A*算法得到的可能不是最佳路徑
總結
AStar算法廣泛運用於遊戲尋路模塊中,初看時覺得很神奇,但是經過學習後,其實會發現其實現邏輯並不複雜。
也能感受到其中的魔力。