单源最短路径快速算法(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中的帖子
    
_spfa_cut算法参考【火星十一郎】在cnblogs中的帖子
    
"""
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)算法的比较
  1. 算法更试用于稀疏图,而且时间效率并不稳定[4]

4. 写在最后

把这块代码上传到pypi.org的时候发现居然404错,说我没有验证邮箱。可能由于pypi在海外的关系,为了等验证邮箱居然花了半个小时。不吐不快。

参考资料

[1] cnblogs SPFA算法 2018-3-5
[2] 码云开源项目 python 3实现的spfa https://gitee.com/snowlandltd/snowland-algorithm-python/blob/master/SApy/graphtheory/spfa/_spfa.py 2018-3-5
[3] csdn 最快最好用的——spfa算法 2018-3-5 【比较推荐这篇,用C++和Pascal实现的】
[4] cnblogs SPFA算法学习笔记 2018-3-5 【同样推荐这篇,java实现,而且用优先队列优化】