Dijkstra最短路算法筆記

最短路算法算是基礎算法, 我還是總是忘。。維基有個動圖很好,比較直觀,可是還不夠友好,于是自己做了點筆記,僅供參考。網上關于Dijkstra的文章也不少,適合的才是最好的。

動態圖 - 來自wiki

Dijkstra_Animation.gif - 無向圖,正權值路徑
  • 頂點為城市,頂點間連線表示城市相連通,線上數字為城市間距離。


    城市間距離
  • 目標為找出從序號為1的城市到序號為5的城市的最短路徑。

  • 該路徑可以由 已知最短路徑 迭代求出。

  • 每次從已知最短路徑中選出沒探索過最短的,從該路徑終點出發,探索新的最短路徑。

  • 一開始,從出發點到達它之外的所有頂點的已知最短路徑為無窮大,到出發點自己的最短路徑為0。

  • 現在,出發點的已知最短路徑最小,則從出發點出發,探索與其直連的所有頂點,如果路徑長度比到該頂點的已知最短路徑小,則刷新該頂點的已知最短路徑

  • 接著,出發點已經探索過了,從未出發探索過的已知最短路徑中選出最小的一個,即從城市2出發,探索與其直連的城市,如果到達該城市的路徑長度比已知最短路徑小,則刷新最短路徑。可以看到,從城市2到3的路徑總長17>城市3目前的最短路徑9,不滿足條件,不刷新城市3的最短路徑,而到城市4的已知最短路徑刷新為7+15=21。(已知最短路徑的計算都是從出發點開始)

  • 依次類推,直到我們遇到目的地是已知最短路徑里未探索的所有頂點中最短的一個時,終止探索。

  • 值得注意的是,我們每一步探索過程中的當前出發點的最短路徑是確定的了不會再變了,因為它是所有未探索過的已知最短路徑中的最小的了,所以不存在從其它地方再加一段路程到達它還會比它更小的情況。

  • 剛看動態圖可能跟不上,我們用工具將gif轉為png一幀幀過一遍就好理解了, 圖在后面,先看代碼。

簡單代碼

  • 維基百科里的偽代碼,可以比較簡單地轉換為python代碼。
V = [ # 頂點列表
      1,  2,  3,  4,  5,  6]

w = [ # 城市間距離, -1表示無窮大,與圖中一致
    [ 0,  7,  9, -1, -1, 14],
    [ 7,  0, 10, 15, -1, -1],
    [ 9, 10,  0, 11, -1,  2],
    [-1, 15, 11,  0,  6, -1],
    [-1, -1, -1,  6,  0,  9],
    [14, -1,  2, -1,  9,  0]]

def Dijkstra(V=V, w=w, s=1, dest=5):
    d = [-1 if v != s else 0 for v in V] # 出發點到各點的已知最短路徑的初始值, 到自身為0
    N = len(V) # 頂點數
    p = [None] * N # 出發點到各點的已知最短路徑的前一個點
    S = set() # 這個集合暫時沒什么用處
    Q = set(range(N)) # 生成所有頂點的虛擬序號的集合, 以0起始
    
    def Extract_Min(Q): # 將頂點集合Q中有最小d[u]值的頂點u從Q中刪除并返回u,可先不管
        u_min = None
        for u in Q:
            if d[u] != -1: # 無窮大則跳過
                u_min = u
        for u in Q:
            if d[u] == -1: # 無窮大則跳過
                continue
            if d[u_min] > d[u]:
                u_min = u
        Q.remove(u_min)
        return u_min
        
    v_d = V.index(dest) # 目的地的虛擬序號
    
    while Q : # 當Q非空
        u = Extract_Min(Q) # 將頂點集合Q中有最小d[u]值的頂點u從Q中刪除并返回u
        if u == v_d : break # 可以在找到目的地之后立即終止,也可繼續查找完所有最短路徑
        S.add(u)
        
        v_reached_from_u = [i for i in range(N) if w[u][i] != -1] # u能到達的頂點, 這里沒有去除在S中的頂點
        for v in v_reached_from_u:
            if d[v] > d[u] + w[u][v] or d[v] == -1: # 如果經過u再到v的距離更短
                d[v] = d[u] + w[u][v] # 用該值更新已知最短路徑
                p[v] = u # 則已知到v的最短路徑上的倒數第二個點為u
        print('d=', d)
    
    v = v_d  # 由p列表可以回溯出最短路徑
    final_route = []
    while v != V.index(s):
        final_route.insert(0, str(V[v]))
        v = p[v]
    final_route.insert(0, str(V[v]))
    print('最終路徑', '-->'.join(final_route))

        
Dijkstra()
#d= [0, 7, 9, -1, -1, 14]
#d= [0, 7, 9, 22, -1, 14]
#d= [0, 7, 9, 20, -1, 11]
#d= [0, 7, 9, 20, 20, 11]
#最終路徑 1-->3-->6-->5

使用堆

  • 關鍵思路中從已知最短路徑中選擇最小的一個,還要增加(更新)新的值,讓人想到堆結構。

  • 利用python的堆結構heapq,可以很簡單地獲取到最小的已知最短路徑。

  • 我們每次從堆中取出已知最短路徑中最短的,將從該點探索下的所有新路徑直接放入堆中,不用管重復,堆會返回有最小路徑的,只要我們額外使用一個集合來記錄已經探索過的頂點即可,使每個頂點只探索一次。

  • 先看沒有記錄路徑只算長度的,代碼相對上面簡潔了不少。

from heapq import heappop, heappush

V = [ # 頂點列表
      1,  2,  3,  4,  5,  6]

w = [ # 城市間距離, -1表示無窮大
    [ 0,  7,  9, -1, -1, 14],
    [ 7,  0, 10, 15, -1, -1],
    [ 9, 10,  0, 11, -1,  2],
    [-1, 15, 11,  0,  6, -1],
    [-1, -1, -1,  6,  0,  9],
    [14, -1,  2, -1,  9,  0]]

def Dijkstra(V=V, w=w, s=1, dest=5):
    N = len(V) # 頂點數
    S = set()
    Q = [(0, V.index(s))] # 路徑長,城市虛擬序號
    
    v_d = V.index(dest)
    
    while Q : # 當Q非空
        d, u = heappop(Q)
        if u == v_d : 
            print(d)
            break # 可以在找到目的地之后立即終止,也可繼續查找完所有最短路徑
        # 如果到目的地的距離已經是當前所有距離中最短的,那不可能會有更短的,所以退出
        if u not in S:
            S.add(u)
            v_reached_from_u = [i for i in range(N) if w[u][i] != -1] # u能到達的頂點
            for v in v_reached_from_u:
                if v not in S:
                    heappush(Q, ((d + w[u][v]), v)) # 到頂點v的某條路徑的距離
                    
Dijkstra() # 20
  • 要記錄路徑,只需要在放入堆中的每個小元組尾部再加上個記號,改動很少
from heapq import heappop, heappush

V = [ # 頂點列表
      1,  2,  3,  4,  5,  6]

w = [ # 城市間距離, -1表示無窮大
    [ 0,  7,  9, -1, -1, 14],
    [ 7,  0, 10, 15, -1, -1],
    [ 9, 10,  0, 11, -1,  2],
    [-1, 15, 11,  0,  6, -1],
    [-1, -1, -1,  6,  0,  9],
    [14, -1,  2, -1,  9,  0]]

def Dijkstra(V=V, w=w, s=1, dest=5):
    N = len(V) # 頂點數
    S = set()
    Q = [(0, V.index(s), str(s))] # 路徑長, 序號, 路徑
    
    v_d = V.index(dest)
    
    while Q : # 當Q非空
        d, u, p = heappop(Q) 
        if u == v_d : 
            print(d, p)
            break # 可以在找到目的地之后立即終止,也可繼續查找完所有最短路徑
        # 如果到目的地的距離已經是當前所有距離中最短的,那不可能會有更短的,所以退出
        if u not in S:
            S.add(u)
            v_reached_from_u = [i for i in range(N) if w[u][i] != -1] # u能到達的頂點
            for v in v_reached_from_u:
                if v not in S:
                    heappush(Q,(
                        (d + w[u][v]), v, ''.join((p,'->',str(V[v])))
                    )) # 到頂點v的某條路徑的距離
                    
Dijkstra() # 20 1->3->6->5

分解圖 - 使用文末工具

Dijkstra_Animation-0.png
Dijkstra_Animation-1.png
Dijkstra_Animation-2.png
Dijkstra_Animation-3.png
Dijkstra_Animation-4.png
Dijkstra_Animation-5.png
Dijkstra_Animation-6.png
Dijkstra_Animation-7.png
Dijkstra_Animation-8.png
Dijkstra_Animation-9.png
Dijkstra_Animation-10.png
Dijkstra_Animation-11.png
Dijkstra_Animation-12.png
Dijkstra_Animation-13.png
Dijkstra_Animation-14.png
Dijkstra_Animation-15.png
Dijkstra_Animation-16.png
Dijkstra_Animation-17.png
Dijkstra_Animation-18.png
Dijkstra_Animation-19.png
Dijkstra_Animation-20.png
Dijkstra_Animation-21.png
Dijkstra_Animation-22.png
Dijkstra_Animation-23.png
Dijkstra_Animation-24.png
Dijkstra_Animation-25.png
Dijkstra_Animation-26.png

工具

  • 在網上找到的效果最好的從gif到png的轉化代碼
# https://gist.github.com/BigglesZX/4016539
import os
from PIL import Image


'''
I searched high and low for solutions to the "extract animated GIF frames in Python"
problem, and after much trial and error came up with the following solution based
on several partial examples around the web (mostly Stack Overflow).
There are two pitfalls that aren't often mentioned when dealing with animated GIFs -
firstly that some files feature per-frame local palettes while some have one global
palette for all frames, and secondly that some GIFs replace the entire image with
each new frame ('full' mode in the code below), and some only update a specific
region ('partial').
This code deals with both those cases by examining the palette and redraw
instructions of each frame. In the latter case this requires a preliminary (usually
partial) iteration of the frames before processing, since the redraw mode needs to
be consistently applied across all frames. I found a couple of examples of
partial-mode GIFs containing the occasional full-frame redraw, which would result
in bad renders of those frames if the mode assessment was only done on a
single-frame basis.
Nov 2012
'''


def analyseImage(path):
    '''
    Pre-process pass over the image to determine the mode (full or additive).
    Necessary as assessing single frames isn't reliable. Need to know the mode 
    before processing all frames.
    '''
    im = Image.open(path)
    results = {
        'size': im.size,
        'mode': 'full',
    }
    try:
        while True:
            if im.tile:
                tile = im.tile[0]
                update_region = tile[1]
                update_region_dimensions = update_region[2:]
                if update_region_dimensions != im.size:
                    results['mode'] = 'partial'
                    break
            im.seek(im.tell() + 1)
    except EOFError:
        pass
    return results


def processImage(path):
    '''
    Iterate the GIF, extracting each frame.
    '''
    mode = analyseImage(path)['mode']
    
    im = Image.open(path)

    i = 0
    p = im.getpalette()
    last_frame = im.convert('RGBA')
    
    try:
        while True:
            print("saving %s (%s) frame %d, %s %s" % (path, mode, i, im.size, im.tile))
            
            '''
            If the GIF uses local colour tables, each frame will have its own palette.
            If not, we need to apply the global palette to the new frame.
            '''
            if not im.getpalette():
                im.putpalette(p)
            
            new_frame = Image.new('RGBA', im.size)
            
            '''
            Is this file a "partial"-mode GIF where frames update a region of a different size to the entire image?
            If so, we need to construct the new frame by pasting it on top of the preceding frames.
            '''
            if mode == 'partial':
                new_frame.paste(last_frame)
            
            new_frame.paste(im, (0,0), im.convert('RGBA'))
            new_frame.save('%s-%d.png' % (''.join(os.path.basename(path).split('.')[:-1]), i), 'PNG')

            i += 1
            last_frame = new_frame
            im.seek(im.tell() + 1)
    except EOFError:
        pass

# 使用
import os
os.chdir(r'E:\python\2017_3_1\dijkst\png')
processImage('E:/python/2017_3_1/dijkst/Dijkstra_Animation.gif')

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,247評論 6 543
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,520評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,362評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,805評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,541評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,896評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,887評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,062評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,608評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,356評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,555評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,077評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,769評論 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,175評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,489評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,289評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,516評論 2 379

推薦閱讀更多精彩內容

  • 現實生活中有很大一類問題可以用簡潔明了的圖論語言來描述,可以轉化為圖論問題。 相關定義 圖可以表示為G=(V, E...
    芥丶未央閱讀 1,742評論 0 7
  • 課程介紹 先修課:概率統計,程序設計實習,集合論與圖論 后續課:算法分析與設計,編譯原理,操作系統,數據庫概論,人...
    ShellyWhen閱讀 2,339評論 0 3
  • 故事的主人公是只有我和當事人所知的一位姑娘,寫此文之前也已取得姑娘的允許。如事有雷同,純屬巧合。 從前有一位姑娘,...
    戀念依舊閱讀 199評論 0 1
  • 高二的暑假,減肥在我的朋友圈子里刮起了一陣龍卷風,身邊人人都想減肥,于是大家就一起相約減肥,跑步打球快走,晚上吃完...
    國際小鄉村閱讀 785評論 0 1
  • 紅翡緣二樓整個成了一個小醫院,各種儀器擺滿了最大那個房間。 林雪初得到了最好的醫療看護,每一個細節都能看出醫院高度...
    可可豆子閱讀 345評論 0 5