A*搜尋演算法(python)
先了解一下什麼是A*演算法。
A*搜尋演算法,俗稱A星演算法。這是一種在圖形平面上,有多個節點的路徑,求出最低通過成本的演算法。常用於遊戲中的NPC(Non-Player-ControlledCharacter)的移動計算,或線上遊戲的BOT(ROBOT)的移動計算上。該演算法像Dijkstra演算法一樣,可以找到一條最短路徑;也像BFS一樣,進行啟發式的搜尋。 A*演算法是一種啟發式搜尋演算法,啟發式搜尋就是在狀態空間中的搜尋對每一個搜尋的位置進行評估,得到最好的位置,再從這個位置進行搜尋直到目標。這樣可以省略大量無謂的搜尋路徑,提高了效率。
A星演算法核心公式:
F = G + H
F - 方塊的總移動代價 G - 開始點到當前方塊的移動代價 H - 當前方塊到結束點的預估移動代價
G值是怎麼計算的? 假設現在我們在某一格子,鄰近有8個格子可走,當我們往上、下、左、右這4個格子走時,移動代價為10;當往左上、左下、右上、右下這4個格子走時,移動代價為14;即走斜線的移動代價為走直線的1.4倍。 這就是G值最基本的計算方式,適用於大多數2.5Drpg頁遊。 根據遊戲需要,G值的計算可以進行拓展。如加上地形因素對尋路的影響。格子地形不同,那麼選擇通過不同地形格子,移動代價肯定不同。同一段路,平地地形和丘陵地形,雖然都可以走,但平地地形顯然更易走。 我們可以給不同地形賦予不同代價因子,來體現出G值的差異。如給平地地形設定代價因子為1,丘陵地形為2,在移動代價相同情況下,平地地形的G值更低,演算法就會傾向選擇G值更小的平地地形。
拓展公式:
G = 移動代價 * 代價因子
H值是如何預估出來的? 很顯然,在只知道當前點,結束點,不知道這兩者的路徑情況下,我們無法精確地確定H值大小,所以只能進行預估。 有多種方式可以預估H值,如曼哈頓距離、歐式距離、對角線估價,最常用最簡單的方法就是使用曼哈頓距離進行預估: H = 當前方塊到結束點的水平距離 + 當前方塊到結束點的垂直距離 題外話:A星演算法之所以被認為是具有啟發策略的演算法,在於其可通過預估H值,降低走彎路的可能性,更容易找到一條更短的路徑。其他不具有啟發策略的演算法,沒有做預估處理,只是窮舉出所有可通行路徑,然後從中挑選一條最短的路徑。這也是A星演算法效率更高的原因。
鑑於前人已經把原理講的很清楚了,便不再廢話,想要深入瞭解下的可以參考下面的兩篇文章。
接下來上程式碼:
程式碼1
檔案AStar.py
# coding=utf-8
#描述AStar演算法中的節點資料
class Point:
"""docstring for point"""
def __init__(self, x = 0, y = 0):
self.x = x
self.y = y
class Node:
def __init__(self, point, g = 0, h = 0):
self.point = point #自己的座標
self.father = None #父節點
self.g = g #g值
self.h = h #h值
"""
估價公式:曼哈頓演算法
"""
def manhattan(self, endNode):
self.h = (abs(endNode.point.x - self.point.x) + abs(endNode.point.y - self.point.y))*10
def setG(self, g):
self.g = g
def setFather(self, node):
self.father = node
class AStar:
"""
A* 演算法
python 2.7
"""
def __init__(self, map2d, startNode, endNode):
"""
map2d: 尋路陣列
startNode: 尋路起點
endNode: 尋路終點
"""
#開放列表
self.openList = []
#封閉列表
self.closeList = []
#地圖資料
self.map2d = map2d
#起點
self.startNode = startNode
#終點
self.endNode = endNode
#當前處理的節點
self.currentNode = startNode
#最後生成的路徑
self.pathlist = [];
return;
def getMinFNode(self):
"""
獲得openlist中F值最小的節點
"""
nodeTemp = self.openList[0]
for node in self.openList:
if node.g + node.h < nodeTemp.g + nodeTemp.h:
nodeTemp = node
return nodeTemp
def nodeInOpenlist(self,node):
for nodeTmp in self.openList:
if nodeTmp.point.x == node.point.x \
and nodeTmp.point.y == node.point.y:
return True
return False
def nodeInCloselist(self,node):
for nodeTmp in self.closeList:
if nodeTmp.point.x == node.point.x \
and nodeTmp.point.y == node.point.y:
return True
return False
def endNodeInOpenList(self):
for nodeTmp in self.openList:
if nodeTmp.point.x == self.endNode.point.x \
and nodeTmp.point.y == self.endNode.point.y:
return True
return False
def getNodeFromOpenList(self,node):
for nodeTmp in self.openList:
if nodeTmp.point.x == node.point.x \
and nodeTmp.point.y == node.point.y:
return nodeTmp
return None
def searchOneNode(self,node):
"""
搜尋一個節點
x為是行座標
y為是列座標
"""
#忽略障礙
if self.map2d.isPass(node.point) != True:
return
#忽略封閉列表
if self.nodeInCloselist(node):
return
#G值計算
if abs(node.point.x - self.currentNode.point.x) == 1 and abs(node.point.y - self.currentNode.point.y) == 1:
gTemp = 14
else:
gTemp = 10
#如果不再openList中,就加入openlist
if self.nodeInOpenlist(node) == False:
node.setG(gTemp)
#H值計算
node.manhattan(self.endNode);
self.openList.append(node)
node.father = self.currentNode
#如果在openList中,判斷currentNode到當前點的G是否更小
#如果更小,就重新計算g值,並且改變father
else:
nodeTmp = self.getNodeFromOpenList(node)
if self.currentNode.g + gTemp < nodeTmp.g:
nodeTmp.g = self.currentNode.g + gTemp
nodeTmp.father = self.currentNode
return;
def searchNear(self):
"""
搜尋節點周圍的點
按照八個方位搜尋
拐角處無法直接到達
(x-1,y-1)(x-1,y)(x-1,y+1)
(x ,y-1)(x ,y)(x ,y+1)
(x+1,y-1)(x+1,y)(x+1,y+1)
"""
if self.map2d.isPass(Point(self.currentNode.point.x - 1, self.currentNode.point.y)) and \
self.map2d.isPass(Point(self.currentNode.point.x, self.currentNode.point.y -1)):
self.searchOneNode(Node(Point(self.currentNode.point.x - 1, self.currentNode.point.y - 1)))
self.searchOneNode(Node(Point(self.currentNode.point.x - 1, self.currentNode.point.y)))
if self.map2d.isPass(Point(self.currentNode.point.x - 1, self.currentNode.point.y)) and \
self.map2d.isPass(Point(self.currentNode.point.x, self.currentNode.point.y + 1)):
self.searchOneNode(Node(Point(self.currentNode.point.x - 1, self.currentNode.point.y + 1)))
self.searchOneNode(Node(Point(self.currentNode.point.x, self.currentNode.point.y - 1)))
self.searchOneNode(Node(Point(self.currentNode.point.x, self.currentNode.point.y + 1)))
if self.map2d.isPass(Point(self.currentNode.point.x, self.currentNode.point.y - 1)) and \
self.map2d.isPass(Point(self.currentNode.point.x + 1, self.currentNode.point.y)):
self.searchOneNode(Node(Point(self.currentNode.point.x + 1, self.currentNode.point.y - 1)))
self.searchOneNode(Node(Point(self.currentNode.point.x + 1, self.currentNode.point.y)))
if self.map2d.isPass(Point(self.currentNode.point.x + 1, self.currentNode.point.y)) and \
self.map2d.isPass(Point(self.currentNode.point.x, self.currentNode.point.y + 1)):
self.searchOneNode(Node(Point(self.currentNode.point.x + 1, self.currentNode.point.y + 1)))
return;
def start(self):
'''''
開始尋路
'''
#將初始節點加入開放列表
self.startNode.manhattan(self.endNode);
self.startNode.setG(0);
self.openList.append(self.startNode)
while True:
#獲取當前開放列表裡F值最小的節點
#並把它新增到封閉列表,從開發列表刪除它
self.currentNode = self.getMinFNode()
self.closeList.append(self.currentNode)
self.openList.remove(self.currentNode)
self.searchNear();
#檢驗是否結束
if self.endNodeInOpenList():
nodeTmp = self.getNodeFromOpenList(self.endNode)
while True:
self.pathlist.append(nodeTmp);
if nodeTmp.father != None:
nodeTmp = nodeTmp.father
else:
return True;
elif len(self.openList) == 0:
return False;
return True;
def setMap(self):
for node in self.pathlist:
self.map2d.setMap(node.point);
return;
檔案2
檔案map2d.py
# coding=utf-8
from __future__ import print_function
class map2d:
"""
地圖資料
"""
def __init__(self):
self.data = [list("####################"),
list("#*****#************#"),
list("#*****#*****#*####*#"),
list("#*########*##******#"),
list("#*****#*****######*#"),
list("#*****#####*#******#"),
list("####**#*****#*######"),
list("#*****#**#**#**#***#"),
list("#**#*****#**#****#*#"),
list("####################")]
self.w = 20
self.h = 10
self.passTag = '*'
self.pathTag = 'o'
def showMap(self):
for x in xrange(0, self.h):
for y in xrange(0, self.w):
print(self.data[x][y], end='')
print(" ")
return;
def setMap(self, point):
self.data[point.x][point.y] = self.pathTag
return;
def isPass(self, point):
if (point.x < 0 or point.x > self.h - 1) or (point.y < 0 or point.y > self.w - 1):
return False;
if self.data[point.x][point.y] == self.passTag:
return True;
檔案3
檔案AStarTest.py
# coding=utf-8
import map2d
import AStar
if __name__ == '__main__':
##構建地圖
mapTest = map2d.map2d();
mapTest.showMap();
##構建A*
aStar = AStar.AStar(mapTest, AStar.Node(AStar.Point(1,1)), AStar.Node(AStar.Point(8,18)))
print "A* start:"
##開始尋路
if aStar.start():
aStar.setMap();
mapTest.showMap();
else:
print "no way"
在AStar.py中增加了對拐角的處理,設定拐角無法直達。
執行結果:
參考: