會一會改變世界的圖算法——Dijkstra(狄克斯特拉)算法

小序

最近在看《算法圖解》這本書,對【狄克斯特拉算法】這一章很有感觸。javascript

狄克斯特拉算法是很是著名的算法,是改變世界的十大算法之一,用於解決【賦權】【有向無環圖】的【單源最短路徑】問題java

若是沒有這種算法,因特網確定沒有如今的高效率。只要能以「圖」模型表示的問題,都能用這個算法找到「圖」中兩個節點間的最短距離。狄克斯特拉算法的穩定性至今仍沒法被取代。node

注:狄克斯特拉算法的原始版本僅適用於找到兩個頂點之間的最短路徑,後來更常見的變體固定了一個頂點做爲源結點而後找到該頂點到圖中全部其它結點的最短路徑,產生一個最短路徑樹(樹是沒有環的圖)。本文討論的是後者。git

定義

若是覺着序言中加紅標粗的這句釋義難理解?讓咱一一拆解,您就明白了。假若知曉概念,可選跳過此節。github

何爲圖

  • 圖由【節點】和【】組成,用來模擬不一樣東西的鏈接關係。

圖 1-1面試

咱們發現咱們太多的現實場景都與圖這種結構相關。人與人之間的關聯,地點與地點之間的關聯,各種拓撲圖等。後文會例舉具體場景案例。算法

何爲有向無環圖

何爲有向?數組

圖 1-1 是無向圖,而圖 1-2 則是有向圖,區別在於後者標註了點與點之間關聯方向。markdown

圖 1-2數據結構

何爲無環?

若是一個有向圖從任意頂點出發沒法通過若干條邊回到該點,則這個圖是一個有向無環圖。

  • Q&A

Q:圖 1-2 是有向無環的嗎?

A:不是,由於 A 通過 C 以後又回到了 A。

圖 1-3

那圖 1-3 是有向無環的嗎?

答:是的,欲知更多在 zh.wikipedia.org/wiki/有向無環圖

何爲賦權

這裏的「權」即「權重」,「賦權」便是給圖的邊賦權重值。

圖 1-4

好比圖 1-4 從點 1 到點 2,須要走 10 步,從點 1 到點 5 須要 100 步,這裏的 10 和 100 即爲「權重值」。

特注:Dijkstra 算法邊權非負。

何爲單源最短路徑

最短路徑是計算給定的兩個節點之間最短(最小權重)的路徑,若是起點肯定,則叫單源最短路徑。

最短路徑有不少現實應用:不少地圖均提供了導航功能,它們就使用了最短路徑算法或其變種。咱們在不少社交平臺上查看某人的簡介時,平臺會展現大家之間有多少共同好友,並列出之間的關係,也是基於此算法。

咱們如今在回看這句定義:

狄克斯特拉算法用於解決【賦權】【有向無環圖】的【單源最短路徑】問題

您是否明瞭?只需緊扣「賦權」、「有向無環圖」、「單源最短路徑」這三個關鍵詞。粗獷點講,這個算法就是用於找兩點之間的最短距離的。

實現

那麼重點來了,狄克斯特拉算法究竟是怎樣實現的呢?

回到《算法圖解》一書,咱們能夠看到最直觀的例子。

圖 2-1

在圖 2-1 中,從起點到終點的最短路徑是多少呢?

若是您使用廣度優先搜索(BFS),獲得的答案將是 7(具體實現,按下不表),但這明顯不是最優解。咱們能夠人眼識別,看出正確答案應該是 6,即從起點 —— 到 B 點 —— 到 A 點 —— 到終點。

若是經過計算機,正確答案是怎麼算出來的呢?正是我們的主角——狄克斯特拉算法

四步走

狄克斯特拉算法包括 4 個步驟:

  1. 找出「最便宜」的節點,便可在最短期內到達的節點。
  2. 更新該節點的鄰居的開銷,其含義將稍後介紹。
  3. 重複這個過程,直到對圖中的每一個節點都這樣作了。
  4. 計算最終路徑。
  • 第一步:找出「最便宜」節點

咱先看第一步,你起點,有兩條路可選,去到 A 需 6 步,去到 B 需 2 步,先無論其它節點,B 點即最便宜節點 記錄如下集合,這點很是重要。

圖 2-2 圖 2-3

  • 第二步:計算通過節點 B 前往各個鄰居所需時間。

起點通過 B 點 到 A 需 5 步,起點通過 B 點 到終點需 7 步,以前的集合中起點到 A 點須要 6 步,到終點是正無窮,如今有了更優解,則須要更新該開銷集合,得出圖 2-4。

圖 2-3 圖 2-4

  • 第三步:重複!!!

如何重複?咱們已經基於 B 點作了更新操做,咱們須要對剩下節點作相似的操做。圖 2-4 表中,除了 B ,A 點的開銷最小,因此咱們須要對 A 點開刀了。—— 「更新節點 A 全部鄰居的開銷。

起點通過 A 點到終點須要 1 步,5 + 1 = 6 ,小於圖 2-4 中終點開銷所需值 7,咱們應該更新開銷集合。

圖 2-5

咱們對每一個節點都採用了狄克斯特拉算法(無需對終點這樣作),因此圖 2-5 是最後的開銷集合,也是最終最優解。從起點到終點最少只需 6 步!

  • 第四步?

細心的朋友可能發現了,說好的四步呢?上面怎麼只有三步?這裏做者在留了個「心機」,其實上面的例子只是算出了最小的開銷的值,並未得出實現最小開銷的最終路徑,即缺乏了一個回溯的過程。

如何計算最終路徑?做者這裏又舉了一個例子,且此例要更爲複雜一些。不過本瓜認爲:狄克斯特拉算法的核心在於第二步、第三步(開銷數組的更新),第四步得出具體路徑只是增長一個父子關係進行回溯補充。

圖 2-6

如圖 2-6 ,問:從曲譜到鋼琴的最短路徑是多少?

答案是: 曲譜 —— 唱片 —— 架子鼓 —— 鋼琴,你知道其中開銷集合的具體更新過程嗎?我想有人面試應該遇到過這題。瞭解更多

本瓜簡述:由點【曲譜】出發,相鄰【唱片】和【海報】兩點,將它們放到開銷數組中,值分別爲 0 和 5。0 小於 5,因此基於【海報】,執行第二步,拿到【曲譜】經過【海報】達到其相鄰的點的值,分別是【吉他】30 和【架子鼓】35,此時開銷數組裏面有四個值:

名稱 開銷
海報 0(已遍歷相鄰值)
唱片 5
吉他 30
架子鼓 35
... ...

5<30<35,進行重複操做,以【唱片】爲基礎,拿到【曲譜】到它相鄰的點的值。分別爲【吉他】20,【架子鼓】25,都小於開銷數組中的值,進行更新。此時的開銷數組爲:

名稱 開銷
海報 0(已遍歷相鄰值)
唱片 5(已遍歷相鄰值)
吉他 20
架子鼓 25
... ...

繼續遍歷,20 < 25,此時應該基於【吉他】,【吉他】與鋼琴相連,【曲譜】經過【唱片】到【吉他】再到【鋼琴】,需 40,更新數組。25 < 40,再基於【架子鼓】遍歷,架子鼓也只和【鋼琴】相連,【曲譜】——【唱片】——【架子鼓】——【鋼琴】,值爲 35,35 小於 40 ,更新。最終只有【鋼琴】這一點沒遍歷,而【鋼琴】又是終點,則執行結束啦。最終是:

名稱 開銷
海報 0(已遍歷相鄰值)
唱片 5(已遍歷相鄰值)
吉他 20(已遍歷相鄰值)
架子鼓 25(已遍歷相鄰值)
鋼琴 35(終點,無需遍歷)

能輕鬆過一遍,算法思想就沒啥問題啦~

其實,最短路徑不必定是物理距離,也能夠轉化其它度量指標,好比錢、時間等等。將生活中的場景抽象成此類算法問題,媽媽不再用擔憂我走彎路了~

狄克斯特拉!牛!

致敬此算法的做者 —— Edsger Wybe Dijkstra,他在1972年得到圖靈獎。

代碼

算法思想很重要,但 TALK IS CHEAP!! 這裏用 py 實現。同時也找到一篇 JS 實現-Finding the Shortest Path in Javascript: Dijkstra’s Algorithm 挖個坑,有空翻譯。/(ㄒoㄒ)/~~

node = find_lowest_cost_node(costs) // 在未處理的節點中找出開銷最小的節點
while node is not None: // 這個while循環在全部節點都被處理事後結束
    cost = costs[node]
    neighbors = graph[node]
    for n in neighbors.keys(): // 遍歷當前節點的全部鄰居
    	new_cost = cost + neighbors[n]
        if costs[n] > new_cost: // 若是經當前節點前往該鄰居更近,
        costs[n] = new_cost // 就更新該鄰居的開銷
        parents[n] = node // 同時將該鄰居的父節點設置爲當前節點
    processed.append(node) // 將當前節點標記爲處理過
    node = find_lowest_cost_node(costs) // 找出接下來要處理的節點,並循環

// 找出開銷最低的節點
def find_lowest_cost_node(costs):
   lowest_cost = float("inf")
   lowest_cost_node = None
   for node in costs: // 遍歷全部的節點
      cost = costs[node]
      if cost < lowest_cost and node not in processed:// 若是當前節點的開銷更低且未處理過,
          lowest_cost = cost // 就將其視爲開銷最低的節點
          lowest_cost_node = node
   return lowest_cost_node
複製代碼

costs 數組即爲開銷數組,能夠獲得最小開銷,也就是最短路徑。

有興趣也可看北大屈婉玲教授的視頻——《單源最短路徑問題及算法》,講的很是清晰。

迷思

美麗心靈

狄克斯特拉算法其實是一個貪婪算法。由於該算法老是試圖優先訪問每一步循環中距離起始點最近的下一個結點。

本瓜正好最近在看一部電影——《美麗心靈》,又加深了對「納什均衡」的認知。

在博弈論中,納什均衡(英語:Nash equilibrium,或稱納什均衡點)是指在包含兩個或以上參與者的非合做博弈(Non-cooperative game)中,假設每一個參與者都知道其餘參與者的均衡策略的狀況下,沒有參與者能夠透過改變自身策略使自身受益時的一個概念解。—— 維基百科

在一個博弈過程當中,不管對方的策略選擇如何,當事人一方都會選擇某個肯定的策略,則該策略被稱做支配性策略。若是任意一位參與者在其餘全部參與者的策略肯定的狀況下,其選擇的策略是最優的,那麼這個組合就被定義爲納什平衡。—— 百度百科

兩者綜合,本瓜產生了困惑:

在這個狄克斯特拉算法中,咱們每走一步都是一次博弈。若是將每一步的博弈交給不一樣的人去作,都達到自身的最優解,那麼最終的解是否必定是最優的呢......?這涉及算法的穩定性?仍是概念混淆了,仍是有點哲學那味了?Anyway, 這東西還挺有意思的。算法、博弈論、最優解......

概念整理

  • 圖算法

「在我所知道的算法中,圖算法應該是最有用的」。—— Aditya Bhargava(《算法圖解》做者)

圖算法有三類核心:路徑搜索、中心性計算、社羣發現。

圖算法還有最基礎的兩個遍歷算法

  1. 廣度優先搜索(BFS)
  2. 深度優先搜索(DFS)

學過《數據結構》的應該都不陌生。同時,BFS 能夠拿出與狄克斯特拉算法作對比,前者可用於在非加權圖中查找最短路徑,後者用於加權圖中。還要提一嘴的是,若是圖的權爲負數,要使用【貝爾曼-福德算法】。有興趣再拓展⑧。

工具

以上

撰文不易,還需鼓勵。年輕人,講點武德 ~ 喜歡就點贊,反感就三連。謝謝~

我是掘金安東尼,一位持續輸出的我的站長~

相關文章
相關標籤/搜索