數(shù)據(jù)結(jié)構(gòu)(十二):最短路徑(Dijkstra算法)

通過上一章最短路徑(Bellman-Ford算法)的內(nèi)容可知多矮,Bellman-Ford 算法是通過重復(fù)對(duì)邊集執(zhí)行松弛函數(shù)褂萧,來逐漸獲得從起點(diǎn)到各個(gè)頂點(diǎn)的最短路徑嗽桩。并且對(duì)邊的松弛順序是隨意進(jìn)行的嗅骄,所以才有最好情況和最壞情況之分床绪。一般情況下則是通過不斷的對(duì)邊集進(jìn)行重復(fù)松弛机杜,來“堆”出從起點(diǎn)到其他頂點(diǎn)的最短路徑帜讲,這種“盲目”的松弛存在極多的無效操作和時(shí)間浪費(fèi)。

Dijkstra 算法使用貪心策略計(jì)算從起點(diǎn)到指定頂點(diǎn)的最短路徑椒拗,通過不斷選擇距離起點(diǎn)最近的頂點(diǎn)似将,來逐漸擴(kuò)大最短路徑權(quán)值获黔,直到覆蓋圖中所有頂點(diǎn)。

Dijkstra 算法前提為圖中邊的權(quán)值非負(fù)在验,若將最短路徑中經(jīng)過的頂點(diǎn)個(gè)數(shù)稱為最短路徑長(zhǎng)度玷氏,則最短路徑長(zhǎng)度與最短路徑權(quán)值呈正相關(guān)。而在 Bellman-Ford 算法中腋舌,因?yàn)檫叺臋?quán)值可能為負(fù)盏触,所以最短路徑長(zhǎng)度較大的頂點(diǎn),其最短路徑權(quán)值不一定更大块饺。所以與 Bellman-Ford 算法相似赞辩,Dijkstra 算法的計(jì)算最短路徑過程,也是呈現(xiàn)一種波紋擴(kuò)散的方式授艰,不同之處在于辨嗽,Bellman-Ford 算法擴(kuò)散過程中,逐漸增大的半徑為最短路徑長(zhǎng)度淮腾,而 Dijkstra 算法的擴(kuò)大半徑為最短路徑權(quán)值糟需。

Dijkstra 算法過程中也存在對(duì)邊的松弛行為,不過該松弛過程并非“盲目”的對(duì)所有邊進(jìn)行松弛来破,而是對(duì)于已確認(rèn)頂點(diǎn)為出度篮灼,未確認(rèn)頂點(diǎn)為入度的邊進(jìn)行松弛。因?yàn)?Dijkstra 算法過程中每個(gè)頂點(diǎn)被確認(rèn)一次徘禁,所以整個(gè)算法過程只需要對(duì)邊集執(zhí)行一次松弛诅诱,即邊的松弛復(fù)雜度為 O(|E|)

算法過程

  1. 從未確認(rèn)頂點(diǎn)中選擇距離起點(diǎn)最近的頂點(diǎn)送朱,并標(biāo)記為已確認(rèn)頂點(diǎn)
  2. 根據(jù)步驟 1 中的已確認(rèn)頂點(diǎn)娘荡,更新其相鄰未確認(rèn)頂點(diǎn)的距離
  3. 重復(fù)步驟 1,2,直到不存在未確認(rèn)頂點(diǎn)

演示示例

graph

以圖 graph 為例驶沼,邊的權(quán)值如圖中所示炮沐,初始化各頂點(diǎn)距離起點(diǎn)權(quán)值為 None,不妨隨機(jī)選擇一個(gè)頂點(diǎn)作為起點(diǎn)回怜,初始化起點(diǎn)的權(quán)值為 0

選擇距離起點(diǎn)最近的頂點(diǎn)為已確認(rèn)頂點(diǎn)大年,并更新該頂點(diǎn)的相鄰未確認(rèn)頂點(diǎn)距離

step 1:

第一次選擇最近的頂點(diǎn)為起點(diǎn)自身,并更新相鄰未確認(rèn)頂點(diǎn)的距離

step 2:

step 3:

step 4:

step 5:

step 6:

step 7:

step 8:

step 9:

算法示例

  1. Dijkstra 算法示例
def dijkstra(graph, start):
    vertices, verticesIndex = [{'index': i, 'weight': None} for i in range(graph.number)], [i for i in range(graph.number)]
    vertices[start - 1]['weight'] = 0
    heapSort(vertices, verticesIndex)
    while len(vertices) > 0:
        swapVertices(vertices, verticesIndex, 0, -1)
        vertex = vertices.pop()
        transformToHeap(vertices, verticesIndex, 0, len(vertices))
        updateDistance(graph, vertices, verticesIndex, vertex)

這里使用 vertices 列表存儲(chǔ)每個(gè)頂點(diǎn)元素玉雾,每個(gè)元素包括兩個(gè)屬性翔试,index 為頂點(diǎn)下標(biāo),weight 為頂點(diǎn)距離起點(diǎn)的大小复旬。算法中使用 verticesIndex 列表存儲(chǔ)每個(gè)頂點(diǎn)元素在 vertices 列表中的下標(biāo)位置垦缅。使用 heapSort 堆排序?qū)γ總€(gè)頂點(diǎn)到起點(diǎn)的距離進(jìn)行排序,即對(duì) vertices 列表進(jìn)行排序驹碍。使用 swapVertices 函數(shù)將 vertices 列表首尾元素交換壁涎,將最小元素放置在列表尾部并執(zhí)行出棧操作凡恍,使用 transformToHeap 函數(shù)調(diào)整 vertices 列表為小頂堆,然后調(diào)用 updateDistance 函數(shù)完成對(duì)相鄰頂點(diǎn)的距離更新怔球。

  1. 交換列表首尾元素
def swapVertices(vertices, verticesIndex, origin, target):
    vertices[origin], vertices[target] = vertices[target], vertices[origin]
    verticesIndex[vertices[origin]['index']], verticesIndex[vertices[target]['index']] = origin, target

當(dāng) vertices 列表調(diào)整為小頂堆之后嚼酝,將列表首、尾元素交換庞溜,則列表尾元素即為距離起點(diǎn)最近的頂點(diǎn)元素革半。

  1. 將列表尾部頂點(diǎn)元素出棧后碑定,更新該頂點(diǎn)的相鄰未確認(rèn)頂點(diǎn)距離起點(diǎn)的權(quán)值
def updateDistance(graph, vertices, verticesIndex, agent):
    node = graph.list[agent['index']]
    while node:
        if verticesIndex[node.index - 1] == -1:  # whether the node in sub graph
            node = node.next
            continue
        vertex = vertices[verticesIndex[node.index - 1]]
        if not vertex['weight'] or vertex['weight'] > agent['weight'] + node.weight:
            vertex['weight'] = agent['weight'] + node.weight
            pos = verticesIndex[vertex['index']]
            while pos > 0 and (not vertices[(pos - 1) // 2]['weight'] or vertices[pos]['weight'] < vertices[(pos - 1) // 2]['weight']):
                swapVertices(vertices, verticesIndex, pos, (pos - 1) // 2)
                pos = (pos - 1) // 2
        node = node.next

對(duì)每一個(gè)相鄰頂點(diǎn)流码,若屬于未確認(rèn)頂點(diǎn),則判斷是否更新到起點(diǎn)的距離延刘。更新距離后的 while 循環(huán)操作漫试,目的為調(diào)整堆結(jié)構(gòu)為小頂堆。

Dijkstra 算法及算法中調(diào)用的函數(shù)都與 prim 算法較大相像碘赖,可以參考最小生成樹prim 算法的部分作輔助理解驾荣。

性能分析

dijkstra 算法中構(gòu)造頂點(diǎn)列表的時(shí)間復(fù)雜度為 O(|V|)。使用堆排序?qū)旤c(diǎn)列表進(jìn)行排序普泡,時(shí)間復(fù)雜度為 O(|V|log |V|)播掷。dijkstra 算法中 while 循環(huán)取權(quán)值最小頂點(diǎn)元素,并調(diào)整元素取出后列表的堆結(jié)構(gòu)撼班,所以調(diào)整復(fù)雜度為 O(|V|log |V|)歧匈;同時(shí),循環(huán)結(jié)構(gòu)內(nèi)執(zhí)行 updateDistance 函數(shù)砰嘁,更新每個(gè)取出頂點(diǎn)的相鄰頂點(diǎn)權(quán)值件炉,所以更新頂點(diǎn)數(shù)為 O(|E|),因?yàn)槊總€(gè)頂點(diǎn)更新距離后矮湘,需要調(diào)整堆結(jié)構(gòu)為小頂堆斟冕,所以更新復(fù)雜度為 O(|E|log |V|)。所以prim 算法的總時(shí)間復(fù)雜度為 O(|E|log |V|)缅阳。

代碼及測(cè)試 github 鏈接:最短路徑(Dijkstra算法)

最后編輯于
?著作權(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