[算法] Dijkstra算法(帶權有向圖 最短路徑算法)


一、帶權有向圖

 

二、算法原理

1)由於我們的節點是從1-6,所以我們創建的列表或數組都是n+1的長度,index=0的部分不使用,循環范圍為1-6(方便計算)。

2)循環之前,我們先初始化dis數組和mark數組:

  dis數組中保存我們需要求的開始點(start),到其余所有點的最短路徑。初始化的時候,只初始化到自己能夠直接到的節點的距離,不能直接到的距離初始化為max_int(即sys.maxsize)。

  mark保存節點的狀態,如果已經被計算過,則狀態為True,還未被計算過,則為False

3)開始循環,注意,我們只循環[1,n]的范圍。index==0不納入循環。

4)N==1時,找到所有Dis元素中,對應mark元素為False的元素。找出其中最小值為10,對應的index為3,也就是節點3。然后在weight數組中,找到3能直接到的節點(且對應mark也要為False),這里找到3能直接到4號節點,且權重為50。此時判斷dis[3]+50<dis[4],如果成立,則使用dis[3]+50更新dis[4]。由於dis[4]等於max_int,所以dis[4]被更新為60。

5)N==2時,找到所有Dis元素中,對應mark元素為False的元素。找出其中最小值為30,對應節點5。然后在weight數組中,找到5能直接到的節點(且對應mark也要為False),為4號和6號節點,且權重為20和60。此時判斷dis[5]+20<dis[4],結果成立,所以dis[4]更新為50。同理dis[6]被更新為90。

6)N==3時,找到所有Dis元素中,對應mark元素為False的元素。找出其中最小值為50,對應節點4。然后在weight數組中,找到4能直接到的節點(且對應mark也要為False),為6號節點,且權重為10。此時判斷dis[4]+10<dis[6],結果成立,所以dis[6]更新為60。

7)N==4時,找到所有Dis元素中,對應mark元素為False的元素。找出其中最小值為60,對應節點6。然后在weight數組中,找到6能直接到的節點(且對應mark也要為False),結果找不到6能直接到的節點。

8)N==5時,找到所有Dis元素中,對應mark元素為False的元素。找出其中最小值為max_int,對應節點2。然后在weight數組中,找到2能直接到的節點(且對應mark也要為False),為3號節點,且權重為5。此時判斷dis[4]+10<dis[6],結果不成立,不成立則不更新dis[3]。

9)N==6時,已經找到到對應mark為False的元素,直接break出循環。整個計算最短路徑的過程結束。

10)可以看到N==6時得到Dis數組的結果是:[max_int,0,max_int,10,50,30,60]。除去index==0的元素,從1-6的元素,即是節點1到所有元素對應的距離(1到2的距離為max_int,說明沒有路線)。

三、Python實現

import numpy as np
import sys


def dijkstra(start, graph_struct, node):
    """
    function:dijkstra
    args:
        start 要計算的起始點
        graph_struct 帶權有向圖的結構
        node 圖中節點個數
    return:
        dis 元素為-1時,表示沒有路徑。其余為距離
    """
    # n表示有N個點,m表示M條邊,x表示求那個點到所有點的最短路徑
    n, m, x = node, len(graph_struct), start
    max_int = sys.maxsize
    weight = np.full([n + 1, n + 1], -1)
    dis = np.full(n + 1, max_int)
    # 初始化權重數組,自己到自己為0.其他為暫為-1
    for i in range(1, n + 1):
        weight[i][i] = 0

    for i in graph_struct:
        # 所有存在邊的位置填上權重,沒有關系的位置保持為-1,表示不可直接到達
        weight[i[0]][i[1]] = i[2]
        # 如果是與我們要求的x點相關的點,則也將x到i的權重填入dis列表中
        if i[0] == x:
            dis[i[1]] = i[2]

    # 程序走到這里,我們就有了權重數組 以及 dis數組(x到各個點的距離,如果沒有邊,則為max_int)
    # dis : [max_int,  0,  max_int,  10,  max_int,  30,  100], dis[0]不納入計算,為了方便,我們只考慮index>=1的部分

    # 定義內部search函數,開始計算x到所有點的最短路徑,最終更新到dis中
    def search(x, dis, weight, n):
        """
        function:search
        args:
            x 要求的點
            dis 距離數組
            weight 權重數組
            n 節點的個數
        return:dis
        """
        mark = np.full(n + 1, False)  # 創建一個mark數組,元素個數為n+1,[1,n]表示1-n點是否被當成最小值加過,已經加過為True,未被加過為False
        mark[x] = True  # 要求的點x,直接標記為加過
        dis[x] = 0  # 自己到自己的距離為0
        count = 1  # 當前已經加了幾個點,當前只有x點,所以初始化為1

        # 開始循環,當count<=n時,說明還有點未被加過
        while count <= n:
            locate = 0  # locate記錄計算出來馬上要被加的點
            min = max_int  # 用於求最小值時比較用
            # 找到dis里面,還沒有加過的位置(mark[idx]=False)里面數的最小值對應的index。
            # dis : [9223372036854775807 0 9223372036854775807 10 9223372036854775807 30 100]
            # mark : [False,True,False,False,False,False],從中找出10的index為 3
            # 該for循環完畢后,min中的值就是最小值10
            for i in range(1, n + 1):
                if not mark[i] and dis[i] < min:
                    min = dis[i]
                    locate = i
            # 如果locate為0,則說明所有點都被加完了,直接退出循環
            if locate == 0: break
            # 如果locate不為0,說明找到了需要加的點,先對其進行標記
            mark[locate] = True
            # 加一個點count增1
            count += 1

            # 從我們找到的需要加的點locate(例如3)開始,看weight數組中他到各個點的距離
            for i in range(1, n + 1):
                # 如果某個點已經被加過了,我們就不計算locate到這個點的距離了
                # 如果locate到某個點的距離為-1,說明沒有路,也不計算
                # 條件3:x到locate的距離(dis[locate]) + locate到點i的距離(weight[locate][i]) < x到i的距離 才能更新
                if not mark[i] and weight[locate][i] != -1 and (
                        dis[locate] + weight[locate][i] < dis[i]):
                    # 條件都滿足,則計算,並更新dis中x-->i的距離
                    dis[i] = dis[locate] + weight[locate][i]

        return dis

    # 調用search開始計算x到各個點的距離,記錄到dis數組中
    dis = search(x, dis, weight, n)

    # 打印dis數組
    for i in range(1, len(dis)):
        if dis[i] == max_int:
            dis[i] = -1
        print("%s點到%s點 %s" % (x, i, "的最短路徑為%s" % dis[i] if dis[i] != max_int else '沒有路'))

    # 返回
    return dis


if __name__ == '__main__':
    # 列舉所有的邊的權重,並寫入weight列表
    weight_init = [(1, 3, 10), (1, 5, 30), (1, 6, 100), (2, 3, 5), (3, 4, 50), (4, 6, 10), (5, 6, 60), (5, 4, 20)]
    dis = dijkstra(1, weight_init, 6)

 

### Dijkstra算法原理還是比較簡單的,但是使用代碼實現的時候比較繞。需要多加復習,熟記於心。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM