在設計基於地圖的游戲,特別是isometric斜45度視角游戲時,幾乎必須要用到最短路徑算法.
Dijkstra算法是尋找當前最優路徑(距離原點最近),如果遇到更短的路徑,則修改路徑(邊松弛).
Astar算法基於Dijkstra算法, 可以理解成, 優先尋找離終點的直線距離最近的路徑.(距離原點近且距離終點也近)
1. 地圖建模
首先要對地圖建模,把地圖抽象成圖,圖由點和有向邊表示.
對45度瓦塊地圖建模,以每個瓦塊的中心是一個點,每個瓦塊有8條邊,指向相鄰的8個瓦塊.
(由於邊可以由節點算出來,所以為了節省內存,可以不保存邊的數據結構)
2. 基礎數據結構
實現Dijkstra算法,要用到以下3個數據結構
vector<const Edge*> _shortestPathTree; // 最短路徑樹
最短路徑樹SPT的key是節點id,value是該節點的一條入邊.由入邊可以找到該節點的上一個節點.
SPT表示圖上起點到任意點的最短路徑.
vector<const Edge*> _searchFrontier; // 當前的搜索邊界
搜索邊界SF的key是節點id,value是該節點的一條入邊.
SF表示當前需要考慮的邊(節點),由於Dijkstra是貪心算法,所以需要把范圍限定在SF尋找局部最優解.
優先隊列PQ
PQ作為SF的輔助數據結構,放入(修改)SF中的點也放入(修改)PQ,
PQ為這些點排序(距原點的代價從小到大). 當邊松弛時,調整PQ里的排序.
優先隊列的數據結構以后寫日志介紹.
搜索邊界SF的key是節點id,value是該節點的一條入邊.
SF表示當前需要考慮的邊(節點),由於Dijkstra是貪心算法,所以需要把范圍限定在SF尋找局部最優解.
優先隊列PQ
PQ作為SF的輔助數據結構,放入(修改)SF中的點也放入(修改)PQ,
PQ為這些點排序(距原點的代價從小到大). 當邊松弛時,調整PQ里的排序.
優先隊列的數據結構以后寫日志介紹.
vector<double> _costs; // 到每個節點的cost
代價COSTS的key是節點id, value是從原點到該點的代價.
3. 算法步驟
(1) 原點O放入SPT,然后O的所有出邊(點)都放入SF
(2) 找到SF上距離O最近的點N(通過PQ)
(3) 遍歷N的每條出邊E指向的點P
(4) 如果P不在SF上,則把<P,E>加入SF
(5) 如果P在SF上且OP新的代價小於老的代價,則修改SF(邊松弛)
(6) 重復(2)-(5)直到把目標放入SPT.
4. A*
A*算法完全基於Dijkstra算法.
只是PQ的排序不是按照當前節點到原點的實際代價_costs
而是按照當前節點到原點的啟發式代價_fCosts
假設點N到原點的實際代價是_costs[N]
則有4種常用方法計算_fCosts
(1) 直線距離啟發因子
_fCosts[N] = _costs[N] + distance(N, O)
(2) 有噪聲的直線距離啟發因子
_fCosts[N ] = _costs[N ] + distance(N, O) * random()
(3) 退化成Dijkstra算法的啟發因子
代價COSTS的key是節點id, value是從原點到該點的代價.
3. 算法步驟
(1) 原點O放入SPT,然后O的所有出邊(點)都放入SF
(2) 找到SF上距離O最近的點N(通過PQ)
(3) 遍歷N的每條出邊E指向的點P
(4) 如果P不在SF上,則把<P,E>加入SF
(5) 如果P在SF上且OP新的代價小於老的代價,則修改SF(邊松弛)
(6) 重復(2)-(5)直到把目標放入SPT.
4. A*
A*算法完全基於Dijkstra算法.
只是PQ的排序不是按照當前節點到原點的實際代價_costs
而是按照當前節點到原點的啟發式代價_fCosts
假設點N到原點的實際代價是_costs[N]
則有4種常用方法計算_fCosts
(1) 直線距離啟發因子
_fCosts[N] = _costs[N] + distance(N, O)
(2) 有噪聲的直線距離啟發因子
_fCosts[N ] = _costs[N ] + distance(N, O) * random()
(3) 退化成Dijkstra算法的啟發因子
_fCosts[N] = _costs[N] + 0
(4) Manhattan啟發因子(在瓦塊地圖游戲中效果最好)
_fCosts[N] = _costs[N] + |position(N-O).x| + |position(N-O).y|