A*算法的實現(Python)

前言

關於A*算法的實現是很早之前的一次開發中的成果,並做了一些改進。當然,在這裏就不記錄改進部分了,因爲其中還有一些爭議。這裏僅是對A*算法的理解和使用Python實現。


參考鏈接

之所以放在前面,是因爲這些鏈接的參考價值特別高,如果希望獲得更多的瞭解,可以通過以下鏈接進行學習。

英文網站

https://www.gamedev.net/reference/articles/article2003.asp

redblobgames(紅色斑點遊戲)

https://www.redblobgames.com/

中文網站

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算法廣泛運用於遊戲尋路模塊中,初看時覺得很神奇,但是經過學習後,其實會發現其實現邏輯並不複雜。

也能感受到其中的魔力。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章