最短路徑之Dijkstra算法和Floyd-Warshall算法


最短路徑算法

最短路徑算法通常用在尋找圖中任意兩個結點之間的最短路徑或者是求全局最短路徑,像是包括Dijkstra、A*、Bellman-Ford、SPFA(Bellman-Ford的改進版本)、Floyd-Warshall、Johnson、BFS等等,這里要集中介紹DijkstraFloyd,前者用來處理任意兩個結點之間的最短路徑,后者處理圖中所有的最短路徑。

Dijkstra算法

理解

其思想為,我們首先紀錄下每個點到原點的距離,這個距離會在每一輪遍歷的過程中刷新。每一個結點到原點的最短路徑是其前驅結點到原點的最短路徑加上前驅結點到當前結點的路徑和。

我們以下面這幅圖片為例,假定起始結點為1,求該結點到其余各個結點的最短路徑。

執行步驟為:

步驟 描述 前驅結點
1 將當前結點到其它結點的路徑都初始化為無窮大
2 依次計算每個結點的路徑長度,此時可以獲得結點1到其它的結點2、3、4、5、6的路徑長度為[7, 9, ∞,∞,14]`,取其中最小的值7,結點2作為前驅結點
3 此時通過結點2可以到達結點4和5,並且其路徑等於起始結點到前驅結點的路徑加上前驅結點到目標結點的路徑長度,因此此時結點1到其它結點3、4、5、6的路徑長度為[17, 22, ∞, ∞],此時將現在的路徑長度和之前的路徑長度進行比較,取對應的較小值,得到最短距離[9, 22, ∞, 14],取其最小值9,結點3更新成前驅結點 2
4 同樣的道理,此時結點1到其它的結點4、5、6的路徑長度為[20, ∞, 11],和之前的路徑長度進行比較,得到最短距離[20, ∞, 11],取最小值11,結點6作為前驅結點 3
5 同樣的道理,得到結點1到其它的結點4, 5的路徑長度為[20, 20],和之前對比,最短距離更新成[20,20] 6
6 因為此時剩下了兩個相等路徑長度的結點,取任意一個結點,結果都是一樣的,所以取哪個都可以

其偽函數如下:

function Dijkstra(G, w, s)
   for each vertex v in V[G]         # 初始化
         d[v] := infinity             # 將各點的已知最短距離先設成無窮大
         previous[v] := undefined     # 各點的已知最短路徑上的前趨都未知
   d[s] := 0                         # 因為出發點到出發點間不需移動任何距離,所以可以直接將s到s的最小距離設為0
   S := empty set
   Q := set of all vertices
   while Q is not an empty set       # Dijkstra演算法主體
         u := Extract_Min(Q)
         S.append(u)
         for each edge outgoing from u as (u,v)
                if d[v] > d[u] + w(u,v)       # 拓展邊(u,v)。w(u,v)為從u到v的路徑長度。
                      d[v] := d[u] + w(u,v)   # 更新路徑長度到更小的那個和值。
                      previous[v] := u        # 紀錄前趨頂點

其中的Extract_Min為提取最小值。

實現

import sys
class Graph():
    def __init__(self, vertices):
        self.V = vertices
        self.graph = [[0 for _ in range(self.V)] for _ in range(self.V)]
    # 打印距離
    def print_distance(self, dist, parent):
        for node in range(self.V):
            print('distance')
            print(node, dist[node])
            print('path')
            self.print_path(parent, node)
            print('---------')
    # 打印路徑
    def print_path(self, parent, j):
        if parent[j] == -1:
            print(j)
            return
        self.print_path(parent, parent[j])
        print(j)
    # 找出最小距離的結點
    def min_distance(self, dist, sptSet):
        min_val = sys.maxsize
        min_index = 0
        for v in range(self.V):
            if dist[v] < min_val and not sptSet[v]:
                min_val = dist[v]
                min_index = v
        return min_index
    # 核心算法
    def dijkstra(self, src):
        dist = [sys.maxsize] * self.V
        dist[src] = 0
        sptSet = [False] * self.V
        parent = [-1] * self.V
        for count in range(self.V):
            # 找出前驅結點
            u = self.min_distance(dist, sptSet)
            sptSet[u] = True
            # 如果某個節點的路徑大於經過前驅結點的路徑,則更新結果成經過前驅結點的路徑
            for v in range(self.V):
                if self.graph[u][v] > 0 and not sptSet[v] and dist[v] > dist[u] + self.graph[u][v]:
                    dist[v] = dist[u] + self.graph[u][v]
                    parent[v] = u
        self.print_distance(dist, parent)
g = Graph(8)
g.graph = [[0, 0, 7, 7, 2, 0, 0, 0],
           [0, 0, 3, 8, 0, 0, 8, 0],
           [7, 3, 0, 0, 0, 7, 0, 0],
           [7, 8, 0, 0, 0, 0, 0, 0],
           [2, 0, 0, 0, 0, 0, 0, 8],
           [0, 0, 7, 0, 0, 0, 8, 0],
           [0, 8, 0, 0, 0, 8, 0, 6],
           [0, 0, 0, 0, 8, 0, 6, 0]]
g.dijkstra(0)

Floyd

理解

其原理為:假定存在某個結點k在結點i和結點j之間,如果i和j之間的路徑長度大於經過k到,則將其距離更新成經過結點k的路徑長度。因此在實現中使用三個循環即可,但是要注意循環的順序,最外層是結點k的遍歷,這樣可以避免i和j的遍歷過早的把最短路徑給定下來。

結果為:

實現

import copy
INF = 1e9
class Graph():
    def __init__(self, graph):
        self.V = len(graph)
        self.graph = graph
    # 打印距離
    def print_distance(self, dist):
        for i in range(self.V):
            for j in range(self.V):
                print('%5s' % (dist[i][j] if dist[i][j] != INF else 'INF'), end='')
            print('')
    # 核心算法
    def floydwarshall(self):
        dist = copy.deepcopy(self.graph)
        for k in range(self.V):
            for i in range(self.V):
                for j in range(self.V):
                    # 不考慮自己指向自己的情況
                    if i == j:
                        continue
                    dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j])
        self.print_distance(dist)
g = Graph([[INF, INF, 1, INF, INF, INF, INF, INF],
           [INF, INF, 2, 8, INF, 5, INF, INF],
           [9, INF, INF, INF, 2, INF, INF, INF],
           [INF, INF, INF, INF, INF, 2, INF, INF],
           [INF, INF, INF, INF, INF, INF, INF, 9],
           [INF, INF, INF, INF, INF, INF, INF, INF],
           [INF, INF, INF, INF, 1, INF, INF, INF],
           [INF, INF, INF, INF, INF, 7, 7, INF]])
g.floydwarshall()


免責聲明!

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



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