Dijkstra最短路算法筆記

最短路算法算是基礎(chǔ)算法筹煮, 我還是總是忘。居夹。維基有個(gè)動(dòng)圖很好败潦,比較直觀,可是還不夠友好准脂,于是自己做了點(diǎn)筆記劫扒,僅供參考。網(wǎng)上關(guān)于Dijkstra的文章也不少狸膏,適合的才是最好的沟饥。

動(dòng)態(tài)圖 - 來自wiki

Dijkstra_Animation.gif - 無向圖,正權(quán)值路徑
  • 頂點(diǎn)為城市湾戳,頂點(diǎn)間連線表示城市相連通版述,線上數(shù)字為城市間距離迁酸。


    城市間距離
  • 目標(biāo)為找出從序號(hào)為1的城市到序號(hào)為5的城市的最短路徑专甩。

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

  • 每次從已知最短路徑中選出沒探索過最短的性昭,從該路徑終點(diǎn)出發(fā)拦止,探索新的最短路徑。

  • 一開始,從出發(fā)點(diǎn)到達(dá)它之外的所有頂點(diǎn)的已知最短路徑為無窮大汹族,到出發(fā)點(diǎn)自己的最短路徑為0萧求。

  • 現(xiàn)在,出發(fā)點(diǎn)的已知最短路徑最小顶瞒,則從出發(fā)點(diǎn)出發(fā)夸政,探索與其直連的所有頂點(diǎn),如果路徑長(zhǎng)度比到該頂點(diǎn)的已知最短路徑小榴徐,則刷新該頂點(diǎn)的已知最短路徑守问。

  • 接著,出發(fā)點(diǎn)已經(jīng)探索過了坑资,從未出發(fā)探索過的已知最短路徑中選出最小的一個(gè)耗帕,即從城市2出發(fā),探索與其直連的城市袱贮,如果到達(dá)該城市的路徑長(zhǎng)度比已知最短路徑小仿便,則刷新最短路徑≡芪。可以看到嗽仪,從城市2到3的路徑總長(zhǎng)17>城市3目前的最短路徑9,不滿足條件柒莉,不刷新城市3的最短路徑闻坚,而到城市4的已知最短路徑刷新為7+15=21。(已知最短路徑的計(jì)算都是從出發(fā)點(diǎn)開始)

  • 依次類推常柄,直到我們遇到目的地是已知最短路徑里未探索的所有頂點(diǎn)中最短的一個(gè)時(shí)鲤氢,終止探索。

  • 值得注意的是西潘,我們每一步探索過程中的當(dāng)前出發(fā)點(diǎn)的最短路徑是確定的了卷玉,不會(huì)再變了,因?yàn)樗撬形刺剿鬟^的已知最短路徑中的最小的了喷市,所以不存在從其它地方再加一段路程到達(dá)它還會(huì)比它更小的情況相种。

  • 剛看動(dòng)態(tài)圖可能跟不上,我們用工具將gif轉(zhuǎn)為png一幀幀過一遍就好理解了品姓, 圖在后面寝并,先看代碼。

簡(jiǎn)單代碼

  • 維基百科里的偽代碼腹备,可以比較簡(jiǎn)單地轉(zhuǎn)換為python代碼衬潦。
V = [ # 頂點(diǎn)列表
      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] # 出發(fā)點(diǎn)到各點(diǎn)的已知最短路徑的初始值, 到自身為0
    N = len(V) # 頂點(diǎn)數(shù)
    p = [None] * N # 出發(fā)點(diǎn)到各點(diǎn)的已知最短路徑的前一個(gè)點(diǎn)
    S = set() # 這個(gè)集合暫時(shí)沒什么用處
    Q = set(range(N)) # 生成所有頂點(diǎn)的虛擬序號(hào)的集合, 以0起始
    
    def Extract_Min(Q): # 將頂點(diǎn)集合Q中有最小d[u]值的頂點(diǎn)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) # 目的地的虛擬序號(hào)
    
    while Q : # 當(dāng)Q非空
        u = Extract_Min(Q) # 將頂點(diǎn)集合Q中有最小d[u]值的頂點(diǎn)u從Q中刪除并返回u
        if u == v_d : break # 可以在找到目的地之后立即終止镀岛,也可繼續(xù)查找完所有最短路徑
        S.add(u)
        
        v_reached_from_u = [i for i in range(N) if w[u][i] != -1] # u能到達(dá)的頂點(diǎn), 這里沒有去除在S中的頂點(diǎn)
        for v in v_reached_from_u:
            if d[v] > d[u] + w[u][v] or d[v] == -1: # 如果經(jīng)過u再到v的距離更短
                d[v] = d[u] + w[u][v] # 用該值更新已知最短路徑
                p[v] = u # 則已知到v的最短路徑上的倒數(shù)第二個(gè)點(diǎn)為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

使用堆

  • 關(guān)鍵思路中從已知最短路徑中選擇最小的一個(gè)弦牡,還要增加(更新)新的值,讓人想到堆結(jié)構(gòu)漂羊。

  • 利用python的堆結(jié)構(gòu)heapq驾锰,可以很簡(jiǎn)單地獲取到最小的已知最短路徑。

  • 我們每次從堆中取出已知最短路徑中最短的走越,將從該點(diǎn)探索下的所有新路徑直接放入堆中椭豫,不用管重復(fù),堆會(huì)返回有最小路徑的旨指,只要我們額外使用一個(gè)集合來記錄已經(jīng)探索過的頂點(diǎn)即可赏酥,使每個(gè)頂點(diǎn)只探索一次。

  • 先看沒有記錄路徑只算長(zhǎng)度的淤毛,代碼相對(duì)上面簡(jiǎn)潔了不少今缚。

from heapq import heappop, heappush

V = [ # 頂點(diǎn)列表
      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) # 頂點(diǎn)數(shù)
    S = set()
    Q = [(0, V.index(s))] # 路徑長(zhǎng),城市虛擬序號(hào)
    
    v_d = V.index(dest)
    
    while Q : # 當(dāng)Q非空
        d, u = heappop(Q)
        if u == v_d : 
            print(d)
            break # 可以在找到目的地之后立即終止低淡,也可繼續(xù)查找完所有最短路徑
        # 如果到目的地的距離已經(jīng)是當(dāng)前所有距離中最短的姓言,那不可能會(huì)有更短的,所以退出
        if u not in S:
            S.add(u)
            v_reached_from_u = [i for i in range(N) if w[u][i] != -1] # u能到達(dá)的頂點(diǎn)
            for v in v_reached_from_u:
                if v not in S:
                    heappush(Q, ((d + w[u][v]), v)) # 到頂點(diǎn)v的某條路徑的距離
                    
Dijkstra() # 20
  • 要記錄路徑蔗蹋,只需要在放入堆中的每個(gè)小元組尾部再加上個(gè)記號(hào)何荚,改動(dòng)很少
from heapq import heappop, heappush

V = [ # 頂點(diǎn)列表
      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) # 頂點(diǎn)數(shù)
    S = set()
    Q = [(0, V.index(s), str(s))] # 路徑長(zhǎng), 序號(hào), 路徑
    
    v_d = V.index(dest)
    
    while Q : # 當(dāng)Q非空
        d, u, p = heappop(Q) 
        if u == v_d : 
            print(d, p)
            break # 可以在找到目的地之后立即終止,也可繼續(xù)查找完所有最短路徑
        # 如果到目的地的距離已經(jīng)是當(dāng)前所有距離中最短的猪杭,那不可能會(huì)有更短的餐塘,所以退出
        if u not in S:
            S.add(u)
            v_reached_from_u = [i for i in range(N) if w[u][i] != -1] # u能到達(dá)的頂點(diǎn)
            for v in v_reached_from_u:
                if v not in S:
                    heappush(Q,(
                        (d + w[u][v]), v, ''.join((p,'->',str(V[v])))
                    )) # 到頂點(diǎn)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

工具

  • 在網(wǎng)上找到的效果最好的從gif到png的轉(zhuǎn)化代碼
# 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')

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市皂吮,隨后出現(xiàn)的幾起案子戒傻,更是在濱河造成了極大的恐慌,老刑警劉巖蜂筹,帶你破解...
    沈念sama閱讀 217,277評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件需纳,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡艺挪,警方通過查閱死者的電腦和手機(jī)不翩,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來麻裳,“玉大人口蝠,你說我怎么就攤上這事〗蚩樱” “怎么了妙蔗?”我有些...
    開封第一講書人閱讀 163,624評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)疆瑰。 經(jīng)常有香客問我眉反,道長(zhǎng)狞谱,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,356評(píng)論 1 293
  • 正文 為了忘掉前任禁漓,我火速辦了婚禮,結(jié)果婚禮上孵睬,老公的妹妹穿的比我還像新娘播歼。我一直安慰自己,他們只是感情好掰读,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評(píng)論 6 392
  • 文/花漫 我一把揭開白布秘狞。 她就那樣靜靜地躺著,像睡著了一般蹈集。 火紅的嫁衣襯著肌膚如雪烁试。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,292評(píng)論 1 301
  • 那天拢肆,我揣著相機(jī)與錄音减响,去河邊找鬼。 笑死郭怪,一個(gè)胖子當(dāng)著我的面吹牛支示,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播鄙才,決...
    沈念sama閱讀 40,135評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼颂鸿,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了攒庵?” 一聲冷哼從身側(cè)響起嘴纺,我...
    開封第一講書人閱讀 38,992評(píng)論 0 275
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎浓冒,沒想到半個(gè)月后栽渴,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,429評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡裆蒸,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評(píng)論 3 334
  • 正文 我和宋清朗相戀三年熔萧,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片僚祷。...
    茶點(diǎn)故事閱讀 39,785評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡佛致,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出辙谜,到底是詐尸還是另有隱情俺榆,我是刑警寧澤,帶...
    沈念sama閱讀 35,492評(píng)論 5 345
  • 正文 年R本政府宣布装哆,位于F島的核電站罐脊,受9級(jí)特大地震影響定嗓,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜萍桌,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評(píng)論 3 328
  • 文/蒙蒙 一宵溅、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧上炎,春花似錦恃逻、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至裳食,卻和暖如春矛市,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背诲祸。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工浊吏, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人烦绳。 一個(gè)月前我還...
    沈念sama閱讀 47,891評(píng)論 2 370
  • 正文 我出身青樓卿捎,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親径密。 傳聞我的和親對(duì)象是個(gè)殘疾皇子午阵,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評(píng)論 2 354

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

  • 現(xiàn)實(shí)生活中有很大一類問題可以用簡(jiǎn)潔明了的圖論語言來描述,可以轉(zhuǎn)化為圖論問題享扔。 相關(guān)定義 圖可以表示為G=(V, E...
    芥丶未央閱讀 1,705評(píng)論 0 7
  • 課程介紹 先修課:概率統(tǒng)計(jì)底桂,程序設(shè)計(jì)實(shí)習(xí),集合論與圖論 后續(xù)課:算法分析與設(shè)計(jì)惧眠,編譯原理籽懦,操作系統(tǒng),數(shù)據(jù)庫(kù)概論氛魁,人...
    ShellyWhen閱讀 2,283評(píng)論 0 3
  • 故事的主人公是只有我和當(dāng)事人所知的一位姑娘暮顺,寫此文之前也已取得姑娘的允許。如事有雷同秀存,純屬巧合捶码。 從前有一位姑娘,...
    戀念依舊閱讀 191評(píng)論 0 1
  • 高二的暑假,減肥在我的朋友圈子里刮起了一陣龍卷風(fēng)澳盐,身邊人人都想減肥祈纯,于是大家就一起相約減肥令宿,跑步打球快走,晚上吃完...
    國(guó)際小鄉(xiāng)村閱讀 764評(píng)論 0 1
  • 紅翡緣二樓整個(gè)成了一個(gè)小醫(yī)院腕窥,各種儀器擺滿了最大那個(gè)房間粒没。 林雪初得到了最好的醫(yī)療看護(hù),每一個(gè)細(xì)節(jié)都能看出醫(yī)院高度...
    可可豆子閱讀 341評(píng)論 0 5