必要知識

如果你不熟悉 A search的話, 可以參考這篇文章(http://theory.stanford.edu/~amitp/GameProgramming/AStarComparison.html) D-Star-Lite演算法和這篇文章都是以A*為基礎做延伸的
D-Star-Lite由Georgia Institute of Technology的Sven Koenig 及 Maxim Likhachev提出, 有興趣可以閱讀原文

D-Star-Lite可以幹麻呢?

搜尋演算法相當重要, 常見的導航系統以及自駕車輛的路徑規劃, 都需要一個搜尋演算法作為核心, 你可能熟悉BFS或是DFS這類的搜尋法, 或是更有效率的A*搜尋

但些算法所假設的環境是靜態的, 無法處理真實世界中的動態環境. 雖然我們可以用簡單暴力的方式做重新搜尋: 只要環境一變動, 立即重新搜尋
但這種方式有非常高的cost, 任何風吹草動都會導致我們要重新搜尋一遍

當環境變化時, 如果我們只更新必要的部份, 而不是計算全部的路徑, 便能省下大量的成本

new old


此時我們就需要D-Star-Lite, Dynamic版本的A*. 它是A*的擴展
當環境發生變化的時候, 它能更新必要的路徑, 有效率的計算新的cost

Min-Queue 與 DP傳播

開始介紹算法觀念之前, 我們先看看A*算法的視覺化

a-star

你可以看到, 計算由起點擴散到終點, 更準確的來說, 計算是由cost低的節點往cost高的節點擴散的. 這個是 min-queueDP擴散所導致的
min-queue確保cost小的node可以比cost高的node更優先計算, 而DP(Dynamic Programming)擴散意味著cost是在node中相互傳遞

這兩個便是A*以及D-Star-Lite的核心概念

Min Queue:



minq

在Min-quque中, 有當前最小值的節點總是優先被選中, 導致計算總是由最小的節點開始, 一步步往cost高的節點移動

DP 傳播公式






dp

從這個式子可以看出, 一個節點的cost的計算依賴於它的母節點
另外, 如果這個節點的cost夠小, 它也會傳播給它的子節點
這種傳播導致了節點會有上游以及下游的概念, 上游節點的資訊傳播給下游節點

D-Star-Lite:

這兩個核心概念如何處理動態環境呢? 相當簡單, 當環境發生變動, 我們便更新對應的節點, 這個被更新的節點便擴散到他的鄰近節點, 鄰近節點再擴散它的周圍,形成一種計算漣漪
DP傳播定義了cost如何擴散, 而Min-queue則決定了擴散的順序

propa

D-Star-Lite引進了兩個數值來實現這個像法, g(s)以及rhs(s)
g(s) 與A*中的g(s)類似, 差別在於這個g值代表著節點s的歷史值
在動態環境中, 我們需要一個機制去記憶cost的變化

至於rhs(s),意思是 right-hand-side value, 它的數值最終會和g(s)相等, 但它的角色是去取得節點s最即時的更新, rhs(s)總是可以拿到比g(s)更新的資訊


當環境變動的時候, 我們可以利用,rhs(s)以及g(s), 來決定是否要更新節點s,


In a node s:
  If rhs(s) == g(s) ---> 更新完畢, s的cost已經計算完成
  If rhs(s) != g(s) ---> 需要更新, cost已經過時了,把s扔進min-queue之中等待更新,

由於我們引進了兩個值的關係, min-quque需要一種新的比大小方法, 我們用兩種key value, K1K2來處理大小的比較

Node u  > v
  when K1(u) > K1(v) or K1(U) == K1(U), K2(u) > K2(v)


與A*相當類似, 差別在於我們現在比較g(s), rhs(s)兩者中的最小值. 另外K1和K2的差別只在於是否引進了heuristic value h(s). 當兩個K1相等的時候, 我們便可以用K2來進行額外的比較

來看看Pseudo code

Main function中 最關鍵的是 ComputerShortestPath() 以及 UpdateVertex(u).
ComputerShortestPath()計算出起點到終點的最小路徑
UpdateVertex() 負責更新環境的變動

main():
  Initialize()
  Forever:
    ComputerShortestPath() #pop each s from min-queue and making g(s) = rhs(s)
      if change happens:
        for all directed edges(u, v) with cost changed:
          update the edge cost c(u, v)
          UpdateVertex(u) #Update the rhs(u) and put it into min-queue

在初始化的過程中, 將所有cost assign為無限大, 除了起點的rhs rhs(s_start) assign成0
這會導致起點的g值與rhs值不一致, 讓演算法開始由起點開始擴散

Initalize():
  for all nodes s in graph:
    g(s) = rhs(s) = INF
  rhs(s_start) = 0
  min_queue.insert(s_start)

在最小路徑的計算中, 我們從min-queue中不斷pop出最小的node, 並計算這個node的cost, 這個while迴圈會不斷執行, 直到我們找到從起到終點的最小路徑, 而且這個數值不能是過時的, 也就是g(s_start) == rhs(s_start)

g(s) > rhs(s), 我們將 g(s) assign 為rhs(s), 代表這個節點完成了更新, 另外這個更新傳播給他的子節點

當情況是 g(s) < rhs(s), 我們將 g(s) 設為無限大, 並更新這個節點自身以及他的子節點
為什麼不跟上一個case一樣, 將rhs(s) 的值塞給 g(s) ? 因為 g(s) 的角色是歷史值. 即時 g(s) 是queue中的最小值, 它很有可能已經過時了, 他的上游可能還在queue中等著呢, 我們利用把它塞回queue的方式, 讓它能夠正確的收斂cost

ComputerShortestPath():
  While min_queue.min() < s_start or g(s_star) != rhs(s_start):  #Keep compute until we find a reliable cost of s_start
    u = min_queue.pop() #Get the minimum of the queue
    if(g(u) > rhs(u)):  #Converge u in this case
      g(u) = rhs(u)
      for all u' in successors of u"
        UpdateVertex(u')
    elif(g(u) < rhs(u)): #node u needs to update the value again.
      g(u) = inf
      UpdateVertex(u')
      for all u' in successors of u"
        UpdateVertex(u')

  min_queue.remove(u) if u in min_queue
  min_queue.insert(u) if g(u) != rhs(u)

節點更新非常簡單, 只要更新按照公式更新rhs值,並把情況符合g(u) != rhs(u)的節點塞進queue中即可

UpdateVertex(u):
  rhs(u) = g(u') + c(u', u) where u' has the minimal rhs among predecessor of u
  min_queue.remove(u) if u in min_queue
  min_queue.insert(u) if g(u) != rhs(u)

來看看D-Star-Lite的計算如何擴散的 dp

如果想知道實做的更多細節,你可以參考我的repo: https://github.com/GBJim/d-star-lite