【原】单源最短路径快速算法(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在海外的关系,为了等验证邮箱居然花了半个小时。不吐不快。

参考资料

[1] cnblogs SPFA算法 https://www.cnblogs.com/shadowland/p/5870640.html 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算法 http://blog.csdn.net/xunalove/article/details/70045815 2018-3-5 【比较推荐这篇,用C++和Pascal实现的】
[4] cnblogs SPFA算法学习笔记 http://www.cnblogs.com/hxsyl/p/3248391.html 2018-3-5 【同样推荐这篇,java实现,而且用优先队列优化】

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
单源最短路径问题是指在一个加权有向图中,找到从一个起始节点到其他所有节点的最短路径。贪心算法是解决这个问题的一种常用方法,其中最著名的算法是Dijkstra算法。 Dijkstra算法的基本思想是从起始节点开始,逐步扩展到其他节点,每次选择当前距离起始节点最近的节点,并更新与该节点相邻的节点的距离。具体步骤如下: 1. 创建一个距离数组dist,用于记录起始节点到其他节点的最短距离。初始时,将起始节点的距离设为0,其他节点的距离设为无穷大。 2. 创建一个集合visited,用于记录已经找到最短路径的节点。 3. 重复以下步骤,直到visited包含所有节点: a. 从未访问过的节点中选择距离起始节点最近的节点u。 b. 将节点u标记为visited。 c. 对于u的每个邻居节点v,如果通过u可以获得更短的距离,则更新v的距离为dist[u] + weight(u, v),其中weight(u, v)表示边(u, v)的权重。 4. 最终,dist数组中记录的就是起始节点到其他所有节点的最短路径。 下面是一个使用Python实现Dijkstra算法的示例代码: ```python import sys def dijkstra(graph, start): # 初始化距离数组 dist = {node: sys.maxsize for node in graph} dist[start] = 0 # 初始化visited集合 visited = set() while len(visited) < len(graph): # 选择距离起始节点最近的节点 min_dist = sys.maxsize min_node = None for node in graph: if node not in visited and dist[node] < min_dist: min_dist = dist[node] min_node = node # 将节点标记为visited visited.add(min_node) # 更新邻居节点的距离 for neighbor, weight in graph[min_node].items(): new_dist = dist[min_node] + weight if new_dist < dist[neighbor]: dist[neighbor] = new_dist return dist # 示例图的邻接表表示 graph = { 'A': {'B': 5, 'C': 3}, 'B': {'A': 5, 'C': 1, 'D': 3}, 'C': {'A': 3, 'B': 1, 'D': 2, 'E': 6}, 'D': {'B': 3, 'C': 2, 'E': 4, 'F': 2}, 'E': {'C': 6, 'D': 4, 'F': 6}, 'F': {'D': 2, 'E': 6} } start_node = 'A' distances = dijkstra(graph, start_node) print(distances) ``` 这段代码实现了Dijkstra算法,并输出了起始节点A到其他节点的最短距离。你可以根据自己的需求修改图的表示和起始节点,以及对结果的处理方式。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

A.Star

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值