1. 程式人生 > >【原】單源最短路徑快速演算法(spfa)的python3.x實現

【原】單源最短路徑快速演算法(spfa)的python3.x實現

單源最短路徑快速演算法(spfa)的python3.x實現

0. 寫在最前面

最近比較忙呢,寫的比較少了。抽空寫了一下這篇文件,簡陋勿噴~(後面準備做個演算法包,包括基礎的資料結構和演算法,感覺任重而道遠)

1. SPFA的簡介[1]

SPFA(Shortest Path Faster Algorithm)演算法是求單源最短路徑的一種演算法,它是Bellman-ford的佇列優化,它是一種十分高效的最短路演算法。
很多時候,給定的圖存在負權邊,這時類似Dijkstra等演算法便沒有了用武之地,而Bellman-Ford演算法的複雜度又過高,SPFA演算法便派上用場了。SPFA的複雜度大約是O(kE),k是每個點的平均進隊次數(一般的,k是一個常數,在稀疏圖中小於2)。
但是,SPFA演算法穩定性較差,在稠密圖中SPFA演算法時間複雜度會退化。
實現方法:建立一個佇列,初始時佇列裡只有起始點,在建立一個表格記錄起始點到所有點的最短路徑(該表格的初始值要賦為極大值,該點到他本身的路徑賦為0)。然後執行鬆弛操作,用佇列裡有的點去重新整理起始點到所有點的最短路,如果重新整理成功且被重新整理點不在佇列中則把該點加入到佇列最後。重複執行直到佇列為空。
此外,SPFA演算法還可以判斷圖中是否有負權環,即一個點入隊次數超過N。

2. python實現[2]

"""
_spfa_simple演算法參考【xunalove】在csdn中的帖子
    http://blog.csdn.net/xunalove/article/details/70045815
_spfa_cut演算法參考【火星十一郎】在cnblogs中的帖子
    http://www.cnblogs.com/hxsyl/p/3248391.html
"""
from queue import Queue
import numpy as np

npa = np.array
npm = np.mat


def spfa(s, node=0, inf=np.inf, method='simple'
)
:
""" 單源最短路的SPFA演算法,Shortest Path Faster Algorithm :param s:距離矩陣(鄰接矩陣表示)其中s[i][j]代表i到j的距離 :param node:源點 :param inf:無窮大值 :param method: 'simple':簡單演算法,針對非負距離(注意不是非負環)有效 :return: """ if method == 'simple': return _spfa_simple(s, node, inf) elif method == 'cut'
: return _spfa_cut(s, node, inf) else: raise ValueError("method not found") def _spfa_simple(s, node=0, inf=np.inf): """ 單源最短路徑演算法, 只對非負權值有效 :param s: 距離矩陣(鄰接矩陣表示)其中s[i][j]代表i到j的距離 :param node:源點 :return: """ a = npa(s) m, n = a.shape if m != n: raise ValueError("s 需要是方陣") dis = np.ones(n) * inf vis = np.zeros(n, dtype=np.int8) dis[node] = 0 vis[node] = 1 que = Queue() prenode = -np.ones(n, dtype=np.int8) # 記錄前驅節點,沒有則用-1表示 que.put(node) while not que.empty(): v = que.get() vis[v] = 0 for i in range(n): temp = dis[v] + a[v][i] if a[v][i] > 0 and dis[i] > temp: dis[i] = temp # 修改最短路 prenode[i] = v if vis[i] == 0: # 如果擴充套件節點i不在佇列中,入隊 que.put(i) vis[i] = 1 return dis, prenode def _spfa_cut(s, node=0, inf=np.inf): """ 單源最短路徑演算法, 只對非負環有效 :param s: 距離矩陣(鄰接矩陣表示)其中s[i][j]代表i到j的距離 :param node:源點 :return: """ a = npa(s) m, n = a.shape if m != n: raise ValueError("s 需要是方陣") count = np.zeros(n, dtype=np.int8) dis = np.ones(n) * inf vis = np.zeros(n, dtype=np.int8) dis[node] = 0 vis[node] = 1 que = Queue() prenode = -np.ones(n, dtype=np.int8) # 記錄前驅節點,沒有則用-1表示 que.put(node) while not que.empty(): v = que.get() vis[v] = 0 for i in range(n): temp = dis[v] + a[v][i] if dis[i] > temp: dis[i] = temp # 修改最短路 prenode[i] = v if vis[i] == 0: # 如果擴充套件節點i不在佇列中,入隊 count[i] += 1 if count[i] > n: raise ValueError("輸入有負環異常") que.put(i) vis[i] = 1 return dis, prenode

3. 改進和說明

  1. 現在的程式碼比較簡陋,以後需要更多的補充。比如

    • 沒有優先佇列優化,可以參考參考資料[4]優化
    • 缺乏測試用例進行測試
    • 缺少與迪傑斯特拉(Dijkstra)演算法和貝爾曼-弗洛伊德(Bellman-Ford)演算法的比較
  2. 演算法更試用於稀疏圖,而且時間效率並不穩定[4]

4. 寫在最後

把這塊程式碼上傳到pypi.org的時候發現居然404錯,說我沒有驗證郵箱。可能由於pypi在海外的關係,為了等驗證郵箱居然花了半個小時。不吐不快。

參考資料