Dijkstra 算法

Dijkstra 算法

前言

  • 為了達(dá)到任意兩結(jié)點(diǎn)的最短路徑扳缕,我們有幾種算法可以實(shí)現(xiàn):Dijkstra 算法逸雹、Floyd 算法等等流酬。

  • Floyd 算法雖然可以得到一幅圖中任意兩點(diǎn)的最小 cost回官,但是我們?cè)诒绢}重點(diǎn)關(guān)注最短路徑 Shortest_path,若要采用 Floyd 算法來得到最短路徑 Shortest_path 不太方便城榛,所以我們決定采用 Dijkstra 算法。

  • 該算法使用了廣度優(yōu)先搜索解決帶權(quán)有向圖的單源最短路徑問題态兴,算法最終得到一個(gè)最短路徑樹狠持。該算法常用于路由算法或者作為其他圖算法的一個(gè)子模塊。Dijkstra 算法無法解決負(fù)權(quán)重的問題瞻润,但所幸在本題中不考慮負(fù)權(quán)重喘垂。

準(zhǔn)備工作

  • 如何描述一幅圖?

  • 用Python的二維列表(2-D list)描述的圖绍撞,這是鄰接矩陣的經(jīng)典表示方法正勒,每行代表一個(gè)結(jié)點(diǎn),每列代表一個(gè)結(jié)點(diǎn)傻铣,如果對(duì)應(yīng)的值為1章贞,則表示兩個(gè)結(jié)點(diǎn)鄰接,如果為 M 則不鄰接非洲,對(duì)于無向無權(quán)圖鸭限,肯定對(duì)稱蜕径。

      nodes = ['s1', 's2', 's3', 's4']
      M =  float("inf")   # M means a large number
      graph_list = [
      [M, 1, 1, 1],
      [1, M, M, 1],
      [1, M, M, 1],
      [1, 1, 1, M],
      ]
    
  • 用 Python 的列表與元組(tuple)描述的圖,這實(shí)際上存儲(chǔ)的是每條邊的信息败京,每個(gè)括號(hào)內(nèi)的內(nèi)容依次為:(tail,head,cost)兜喻。

      graph_edges = [
      (‘s1’, ‘s2’, 1), (‘s1’, ‘s3’, 1), (‘s1’, ‘s4’, 1),
      (‘s2’, ‘s1’, 1), (‘s2’, ‘s4’, 1),
      (‘s3’, ‘s1’, 1), (‘s3’, ‘s4’, 1),
      (‘s4’, ‘s1’, 1), (‘s4’, ‘s2’, 1), (‘s4’, ‘s3’, 1),
      ]
    
  • 用 Python 的字典(dict)描述的圖,這種表示方法很靈活赡麦,每個(gè)key代表一個(gè)結(jié)點(diǎn)朴皆,該結(jié)點(diǎn)作為tail,其鄰接的head及它們之間的cost存儲(chǔ)在value里泛粹,可想而知遂铡,對(duì)于每個(gè) tail,可能有多個(gè) head戚扳,所以 value 其實(shí)是一個(gè)列表忧便,每個(gè)列表項(xiàng)是個(gè)兩元組 (head, cost)。

      graph_dict = {
      ‘s1’:[(‘s2’,1),(‘s3’,1),(‘s4’,1)],
      ‘s2’:[(‘s1’,1), (‘s4’,1)],
      ‘s3’:[(‘s1’,1), (‘s4’,1)],
      ‘s4’:[(‘s1’,1),(‘s2’,1),(‘s3’,1)],
      }
    
  • 在有些Python代碼中帽借,也有這樣的形式:‘s1’:{‘s2’:1,‘s3’:1,‘s4’:1}珠增,這和我們的 ‘s1’:[(‘s2’,1),(‘s3’,1),(‘s4’,1)] 沒什么差別,都是我們用來存儲(chǔ)數(shù)據(jù)的數(shù)據(jù)結(jié)構(gòu)砍艾。

  • 這三種表示方法在是可以互相轉(zhuǎn)化蒂教,在此我們給出 graph_list -> graph_edges 以及 graph_edges->graph_dict 的轉(zhuǎn)化算法。

      # graph_list -> graph_edges
      graph_edges = []
      for i in nodes:
          for j in nodes:
              if i!=j and graph_list[nodes.index(i)][nodes.index(j)]!=M:
                  graph_edges.append((i,j,graph_list[nodes.index(i)][nodes.index(j)]))
    
      # graph_edges->graph_dict
      graph_dict = defaultdict(list)
      for tail,head,cost in graph_edges:
          graph_dict[tail].append((head,cost))
    

算法描述

  • 將圖所有點(diǎn)的集合 S 分為兩部分脆荷,V 和 U凝垛。

  • V 集合是已經(jīng)得到最短路徑的點(diǎn)的集合,在初始情況下 V 中只有源點(diǎn) s蜓谋,U 是還未得到最短路徑點(diǎn)的集合梦皮,初始情況下是除 s 的所有點(diǎn)。因?yàn)槊看蔚枰该鳟?dāng)前正在迭代的 V 集合中的某點(diǎn)桃焕,所以將該點(diǎn)設(shè)為中間點(diǎn)剑肯。自然,首先應(yīng)將 s 設(shè)為中間點(diǎn) k观堂,然后開始迭代让网。

  • 在每一次迭代過程中,取得 U 中距離 k 最短的點(diǎn) k师痕,將 k 加到 V 集合中溃睹,將 k 從 U 集合刪除,再將 k 設(shè)為中間點(diǎn) v胰坟。重復(fù)此過程直到 U 集合為空因篇。

Python 實(shí)現(xiàn) Dijkstra 算法

  • 一般而言,若想尋找給定兩點(diǎn)的最短路徑,Dijkstra 算法必須傳入三個(gè)參數(shù)惜犀,一個(gè)是圖的描述 graph_dict铛碑,一個(gè)是源點(diǎn) from_node,一個(gè)是終點(diǎn) to_node虽界。

  • 核心代碼如下:

      def dijkstra(graph_dict, from_node, to_node):
          cost = -1
          ret_path=[]
          q, seen = [(0,from_node,())], set()
          while q:
              (cost,v1,path) = heappop(q)
              if v1 not in seen:
                  seen.add(v1)
                  path = (v1, path)
                  if v1 == to_node: # Find the to_node!!!
                      break;
                  for v2,c in graph_dict.get(v1, ()):
                      if v2 not in seen:
                          heappush(q, (cost+c, v2, path))
    
          # Check the way to quit 'while' loop!!!
          if v1 != to_node:
              print("There is no node: " + str(to_node))
              cost = -1
              ret_path=[]
    
          # IF there is a path from from_node to to_node, THEN format the path!!!
          if len(path)>0:
              left = path[0]
              ret_path.append(left)
              right = path[1]
              while len(right)>0:
                  left = right[0]
                  ret_path.append(left)
                  right = right[1]
              ret_path.reverse()
    
          return cost,ret_path
    

測(cè)試

  • 不失一般性汽烦,給定一個(gè)帶權(quán)有向圖:

      graph_list = [
      [0,30,15,M,M,M],
      [5,0,M,M,20,30],
      [M,10,0,M,M,15],
      [M,M,M,0,M,M],
      [M,M,M,10,0,M],
      [M,M,M,30,10,0]
      ]
    
  • 其表示的圖如下:

    graph
  • 測(cè)試一下 s1 到 s6 的最短路徑:

    test
  • 可以看到,dijkstra 函數(shù)得到了正確的最短路徑以及 cost 值莉御。

算法詳解

  • 這里用到了堆排序的 heapq 模塊撇吞,注意它的 heappop(q) 與heappush(q,item) 方法:

    • heapq.heappush(heap, item): 將 item 壓入到堆數(shù)組 heap 中。如果不進(jìn)行此步操作礁叔,后面的 heappop() 失效

    • heapq.heappop(heap): 從堆數(shù)組 heap 中取出最小的值牍颈,并返回。

思路:

  1. q, seen = [(0,from_node,())], set()

    • q 記錄了中間點(diǎn) v 與 U 集合中哪些點(diǎn)鄰接琅关,這些鄰接點(diǎn)為 k1煮岁、k2...,并且在 q 中的存儲(chǔ)形式為:[(cost1,k1,path1),(cost2,k2,path2)...]

    • seen 就是算法描述部分提到的 V 集合涣易,記錄了所有已訪問的點(diǎn)

  2. (cost,v1,path) = heappop(q)

    • 這行代碼會(huì)得到 q 中的最小值画机,也就是在算法描述部分提到的 k,用算法描述為:cost=min(cost1,cost2...)
  3. seen.add(v1)

    • 這行代碼對(duì)應(yīng)算法描述的“ k 加到 V 集合中新症,將 k 從 U 集合刪除”

    • 這個(gè)時(shí)候的 k 已經(jīng)成為了中間點(diǎn) v

  4. 查找 U 集合中所有與中間點(diǎn) v 鄰接的點(diǎn) k1步氏、k2... :

         for v2,c in graph_dict.get(v1, ()):
             if v2 not in seen:
                 heappush(q, (cost+c, v2, path))
    
    • 把 k1、k2... push 進(jìn)入 q 中徒爹,回到第 2 點(diǎn)

利用 dijkstra 得到圖中所有最短路徑

  • 我們準(zhǔn)備在此基礎(chǔ)上加以改進(jìn)荚醒,利用 Dijkstra 算法得到任意兩個(gè)結(jié)點(diǎn)之間的最短路徑,為了達(dá)到這個(gè)目的隆嗅,我們?cè)谒惴ǖ淖詈笠幸粋€(gè)數(shù)據(jù)結(jié)構(gòu)來存儲(chǔ)這些最短路徑界阁,如果使用 Python,這個(gè)數(shù)據(jù)結(jié)構(gòu)應(yīng)該是像下面這樣的:

      Shortest_path_dict = {
      's1': {'s2': ['s1', 's3', 's2'], 's3': ['s1', 's3'] },
      's2': {'s1': ['s2', 's1'], 's3': ['s2', 's1', 's3'},
      's3': {'s1': ['s3', 's2', 's1'], 's2': ['s3', 's2'] },
      }
    
  • 它存儲(chǔ)了下面這幅圖的所有最短路徑:

    graph2
  • 我們要想得到任意兩點(diǎn)的最短路徑胖喳,要用 Shortest_path_dict 來存儲(chǔ)所有可能的最短路徑铺董,于是再創(chuàng)建一個(gè)新的函數(shù) dijkstra_all,該函數(shù)僅僅只需要接受一個(gè)參數(shù):圖的描述 graph_dict禀晓,然后返回 Shortest_path_dict,在 dijkstra_all 中需要循環(huán)調(diào)用 dijkstra 函數(shù)坝锰。

  • 核心代碼如下:

      def dijkstra_all(graph_dict):
          Shortest_path_dict = defaultdict(dict)
          for i in nodes:
              for j in nodes:
                  if i != j:
                      cost,Shortest_path = dijkstra(graph_dict,i,j)
                      Shortest_path_dict[i][j] = Shortest_path
    
          return Shortest_path_dict
    
  • 不失一般性粹懒,我們采用帶權(quán)有向圖測(cè)試我們的算法,圖的描述與前文測(cè)試 dijkstra 算法時(shí)一致顷级,在此直接調(diào)用 dijkstra_all函數(shù)凫乖,傳入 graph_dict,得到的結(jié)果截圖如下:

    test2
  • 看最后的輸出,得到了我們想要的 Shortest_path_dict

源碼:https://github.com/edisonleolhl/DataStructure-Algorithm/blob/master/Graph/ShortestPath/dijkstra.py

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末帽芽,一起剝皮案震驚了整個(gè)濱河市删掀,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌导街,老刑警劉巖披泪,帶你破解...
    沈念sama閱讀 218,122評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異搬瑰,居然都是意外死亡款票,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門泽论,熙熙樓的掌柜王于貴愁眉苦臉地迎上來艾少,“玉大人,你說我怎么就攤上這事翼悴「抗唬” “怎么了?”我有些...
    開封第一講書人閱讀 164,491評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵鹦赎,是天一觀的道長(zhǎng)谍椅。 經(jīng)常有香客問我,道長(zhǎng)钙姊,這世上最難降的妖魔是什么毯辅? 我笑而不...
    開封第一講書人閱讀 58,636評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮煞额,結(jié)果婚禮上思恐,老公的妹妹穿的比我還像新娘。我一直安慰自己膊毁,他們只是感情好胀莹,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,676評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著婚温,像睡著了一般描焰。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上栅螟,一...
    開封第一講書人閱讀 51,541評(píng)論 1 305
  • 那天荆秦,我揣著相機(jī)與錄音,去河邊找鬼力图。 笑死步绸,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的吃媒。 我是一名探鬼主播瓤介,決...
    沈念sama閱讀 40,292評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼吕喘,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了刑桑?” 一聲冷哼從身側(cè)響起氯质,我...
    開封第一講書人閱讀 39,211評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎祠斧,沒想到半個(gè)月后闻察,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,655評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡梁肿,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,846評(píng)論 3 336
  • 正文 我和宋清朗相戀三年蜓陌,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片吩蔑。...
    茶點(diǎn)故事閱讀 39,965評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡钮热,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出烛芬,到底是詐尸還是另有隱情隧期,我是刑警寧澤,帶...
    沈念sama閱讀 35,684評(píng)論 5 347
  • 正文 年R本政府宣布赘娄,位于F島的核電站仆潮,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏遣臼。R本人自食惡果不足惜性置,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,295評(píng)論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望揍堰。 院中可真熱鬧鹏浅,春花似錦、人聲如沸屏歹。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)蝙眶。三九已至季希,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間幽纷,已是汗流浹背式塌。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留友浸,地道東北人峰尝。 一個(gè)月前我還...
    沈念sama閱讀 48,126評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像尾菇,于是被迫代替她去往敵國(guó)和親境析。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,914評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容