给定一个带权有向图G=(V,E),其中每条边的权是一个实数。另外,还给定V中的一个顶点,称为源。要计算从源到其他所有各顶点的最短路径长度。这里的长度就是指路上各边权之和。这个问题通常称为单源最短路径问题。

1.Floyd

Floyd是一种用于寻找图中每一对定点之间最短路径的算法。核心思想是对于每一对顶点u和v,寻找是否存在一个顶点w,使得从u到w再到v比已知的路径更短,如果是则更新它。

时间复杂度O(n3)

m, n = map(int, input().split())
graph = [[False] * m for _ in range(m)]
for _ in range(n):i, j, value = map(int, input().split())graph[i][j] = valuegraph[j][i] = value
def floyd(graph):for k in range(len(graph)):  # 更新次数  # 以下两层循环为遍历矩阵每个元素,根据递推关系更新每两个节点之间的路径权值for i in range(len(graph) - 1, -1, -1):for j in range(len(graph)):if graph[i][k] and graph[k][j] and (not graph[i][j] or graph[i][k] + graph[k][j] < graph[i][j]):graph[i][j] = graph[i][k] + graph[k][j]
floyd(graph)
print(graph)'''
6 8
0 1 3
0 3 2
1 2 1
2 3 8
2 4 12
3 4 3
3 5 4
4 5 2
'''
# out: [[4, 3, 4, 2, 5, 6], [3, 2, 1, 5, 8, 9], [4, 1, 2, 6, 9, 10], [2, 5, 6, 4, 3, 4], [5, 8, 9, 3, 4, 2], [6, 9, 10, 4, 2, 4]]

2.最小生成树

参考讲解

问题描述:输入:第一行输入两个整数,分别表示节点数m、边数n接下来总共有n行,每一行有三个数,前两个表示相连的节点编号(0,1,2,...,n),最后一个数表示该边的权重值输出:一个整数,表示将所有点连起来权重的最小值。示例:
输入:
9 15
0 1 3
0 5 4
1 2 8
1 6 6
1 8 5
2 8 2
2 3 12
3 8 11
3 6 14
3 7 6
3 4 10
4 7 1
4 5 18
5 6 7
6 7 9
输出:
36  # path: [(4, 7), (2, 8), (0, 1), (0, 5), (1, 8), (1, 6), (3, 7), (6, 7)]

克鲁斯卡尔算法(Kruskal)
1.对边集合进行排序;
2.选择最小权值边,若不构成环,则添加到集合边A中;
3.重复执行步骤2,直到添加|V| - 1 条边。

Kruskal基于边操作,适用于边少点多的场景,多用邻接表存储。
时间复杂度:O(ElogE) or O(ElogV)

m, n = map(int, input().split())
a = [[] for _ in range(n)]
for i in range(n):a[i] = list(map(int, input().split())) # 邻接表存储
a.sort(key = lambda x: x[2])
res = 0
path = []
def find(father, f):while father[f] > 0:f = father[f]return f
father = {i : 0 for i in range(m)}  # 注意:字典的键是不可变的,可以是数字、字符串、元组,但不能为列表!!!
for i in range(n):node1 = find(father, a[i][0])node2 = find(father, a[i][1])if node1 != node2:father[node1] = node2res += a[i][2]path.append((a[i][0], a[i][1]))if len(path) == m - 1: break
print(res)
print(path)

Prim算法
Kruskal算法的过程为不断对子图进行合并,直到形成最终的最小生成树。Prim算法的过程则是只存在一个子图,
不断选择顶点加入到该子图中,即通过对子图进行扩张,直到形成最终的最小生成树。

算法过程如下:
1.按照距离子图的远近,对顶点集合进行排序;
2.选择最近的顶点加入到子图中,并更新相邻顶点对子图的距离;
3.重复步骤2,直到顶点集合为空。

prim基于顶点操作,适用于点少边多的场景,多用邻接矩阵存储.
时间复杂度:O(n**2)

m, n = map(int, input().split())
a = [[float('inf')] * m for _ in range(m)]  # 用邻接矩阵存储,Kruskal是邻接表存储!!!
for _ in range(n):i, j, v = map(int, input().split())a[i][j] = va[j][i] = v
res, path = 0, []
print(a)
# weightE保存相关顶点间边的权值, V保存相关顶点下标
weightE, V = [0] * m, [0] * m
#将V0作为最小生成树的根开始遍历,权值为0
for i in range(1, m):weightE[i] = a[0][i]  # 将邻接矩阵第0行所有权值先加入数组
# 真正构造最小生成树的过程
for i in range(1, m):min_w = float('inf')j, k = 1, 0while j < m:  # 遍历全部顶点if weightE[j] and weightE[j] < min_w:min_w = weightE[j]k = jj += 1res += a[V[k]][k]path.append([V[k], k])weightE[k] = 0  # 将当前顶点的权值设置为0,表示此顶点已经完成任务,进行下一个顶点的遍历for j in range(1, m):if weightE[j] and a[k][j] < weightE[j]:weightE[j] = a[k][j]V[j] = k
print(res)
print(path)

3.迪杰斯特拉Dijkstra算法

参考讲解

Dijkstra算法主要针对的是有向图的单元最短路径问题,且不能出现权值为负的情况!Dijkstra算法类似于贪心算法计算从起点到指定顶点的最短路径,通过不断选择距离起点最近的顶点,来逐渐扩大最短路径权值,直到覆盖图中所有顶点(与方法4不同,其对边的松弛顺序是随意进行的)。其应用根本在于最短路径的最优子结构性质。Dijkstra算法步骤与Floyd算法类似,更新规则也是dist[j]=min(dist[j],dist[i]+matrix[i][j])

采用邻接矩阵存储,两顶点之间不存在有向连接,则设置为inf.

算法过程:
1.从未确认顶点中选择距离起点最近的顶点,并标记为已确认顶点
2.根据步骤 1 中的已确认顶点,更新其相邻未确认顶点的距离
3.重复步骤 1,2,直到不存在未确认顶点

# 图中的顶点数
V = 6
# 标记数组:used[v]值为False说明改顶点还没有访问过,在S中,否则在U中!
used = [False for _ in range(V)]
# 距离数组:distance[i]表示从源点s到i的最短距离,distance[s]=0
distance = [float('inf') for _ in range(V)]
# cost[u][v]表示边e=(u,v)的权值,不存在时设为INF
cost = [[float('inf') for _ in range(V)] for _ in range(V)]def dijkstra(s):distance[s] = 0while True:# v在这里相当于是一个哨兵,对包含起点s做统一处理!v = -1# 从未使用过的顶点中选择一个距离最小的顶点for u in range(V):if not used[u] and (v == -1 or distance[u] < distance[v]):v = uif v == -1:  # 说明所有顶点都维护到S中了!break# 将选定的顶点加入到S中, 同时进行距离更新used[v] = True# 更新U中各个顶点到起点s的距离。之所以更新U中顶点的距离,是由于上一步中确定了k是求出最短路径的顶点,从而可以利用k来更新其它顶点的距离;例如,(s,v)的距离可能大于(s,k)+(k,v)的距离。for u in range(V):distance[u] = min(distance[u], distance[v] + cost[v][u])if __name__ == '__main__':Inf = float('inf')cost = [[0, 1, 12, Inf, Inf, Inf],[Inf, 0, 9, 3, Inf, Inf],[Inf, Inf, 0, Inf, 5, Inf],[Inf, Inf, 4, 0, 13, 15],[Inf, Inf, Inf, Inf, 0, 4],[Inf, Inf, Inf, Inf, Inf, 0]]s = int(input('请输入一个起始点(0-5):'))dijkstra(s)print(distance)  # [0, 1, 8, 4, 13, 17]

4.贝尔曼-福特Bellman-Ford算法

参考讲解

算法核心是通过松弛函数进行距离更新:if d[v] > d[u] + w(u, v): d[v] = d[u] + w(u, v),最多通过|V - 1|次松弛就可以得到各顶点之间的最短距离。

787. K 站中转内最便宜的航班

class Solution:def findCheapestPrice(self, n: int, flights: List[List[int]], src: int, dst: int, K: int) -> int:distance = [[float('inf')] * (K + 2) for _ in range(n)]  # distance行表示点,列表示松弛次数,值代表最短距离for i in range(K + 2):distance[src][i] = 0  # 原点不管松弛几次距离本身都是0times = 1  # 松弛次数def relax(vi, vj, w, times):if distance[vj][times] > distance[vi][times - 1] + w:distance[vj][times] = distance[vi][times - 1] + wwhile times < K + 2:  # 我们这里把直连一个点设置为松弛1次,绕过一个点算松弛2次,绕过两个点算松弛3次,以此类推,可以绕过K个点,那么可以松弛K+1次for vi, vj, w in flights:if distance[vi][times - 1] != None:relax(vi, vj, w, times)times += 1return distance[dst][K + 1] if distance[dst][K + 1] != float('inf') else -1

优化版本:

class Solution(object):def findCheapestPrice(self, n, flights, src, dst, k):dist = [float('inf')] * n  # dist[v]表示到达v的最小花费dist[src] = 0for i in range(k + 1):  # 对每条边做 k+1 次松弛操作,每次松弛操作实际上是对相邻节点的访问,所以总的中转为k次dist_old = [_ for _ in dist]for u, v, w in flights:dist[v] = min(dist[v], dist_old[u] + w)return dist[dst] if dist[dst] != float('inf') else -1

5.SPFA算法

参考讲解

SPFA(Shortest Path Faster Algorithm)算法是求单源最短路径的一种算法,它是Bellman-ford的队列优化,它是一种十分高效的最短路算法。

很多时候,给定的图存在负权边,这时类似Dijkstra等算法便没有了用武之地,而Bellman-Ford算法的复杂度又过高,SPFA算法便派上用场了。SPFA的复杂度大约是O(kE),k是每个点的平均进队次数(一般的,k是一个常数,在稀疏图中小于2)。

但是,SPFA算法稳定性较差,在稠密图中SPFA算法时间复杂度会退化。

实现方法:
建立一个队列,初始时队列里只有起始点,再建立一个表格记录起始点到所有点的最短路径(该表格的初始值要赋为极大值,该点到他本身的路径赋为0)。然后执行松弛操作,用队列里有的点去刷新起始点到所有点的最短路,如果刷新成功且被刷新点不在队列中则把该点加入到队列最后。重复执行直到队列为空。

此外,SPFA算法还可以判断图中是否有负权环,即一个点入队次数超过N。

from collections import deque
class Solutions:def spfa(self, grids, src, target):dis = [float('inf')] * len(grids)counter = [0] * len(grids)dis[src] = 0visited = set()  #这里设置一个visited集合主要是为了后面判断是否存在负环,不需判断负环的代码请看下面简洁代码q = deque([(0, src)])visited.add(src)while q:d_cur, cur = q.popleft()visited.remove(cur)for node, value in enumerate(grids[cur]):if value and dis[cur] + value < dis[node]:dis[node] = value + dis[cur]if node not in visited:counter[node] += 1if counter[node] > len(grids):raise ValueError('输入有负环异常!')visited.add(node)q.append((dis[node], node))return dis[target] if dis[target] != float('inf') else -1m, n, src, target = map(int, input().split())
grids = [[float('inf')] * m for _ in range(m)]
for _ in range(n):i, j, dis = map(int, input().split())grids[i][j] = dis
f = Solutions()
out = f.spfa(grids, src, target)
print(out)
'''
输入:
第一行节点数m、边数n、起点、终点,接下来n行每行三个数字,当前起点-下一终点-距离值。
输出起点到终点的最短距离。
input:
5 7 0 4
0 1 3
0 2 2
1 3 1
3 0 4
3 2 6
3 4 4
4 2 4
output:
8
'''

简洁版:

from collections import deque
from collections import defaultdict
class Solution:def spfa(self, grids, src, target):dist = [float('inf')] * len(grids)dist[src] = 0dic = defaultdict(dict)for u, v, w in grids:dic[u][v] = wq = deque([src])while q:node = q.popleft()for v in dic.get(node, []):if dist[node] + dic[node][v] < dist[v]:dist[v] = dist[node] + dic[node][v]q.append(v)return dist[target] if dist[target] != float('inf') else -1

总结

就放一个各种算法对比表吧。

Python单源最短路径算法汇总相关推荐

  1. Bellman-Ford 单源最短路径算法

    Bellman-Ford 单源最短路径算法 Bellman-Ford 算法是一种用于计算带权有向图中单源最短路径(SSSP:Single-Source Shortest Path)的算法.该算法由 R ...

  2. C++实现dijkstra单源最短路径算法-邻接表+优先队列

    dijkstra单源最短路径算法不允许边权值为负,适用的图范围可以很大. 代码如下: #include <iostream> #include <queue> #include ...

  3. 四种不同单源最短路径算法性能比较

    四种不同单源最短路径算法性能比较   一.最短路径问题描述 单源最短路径描述:给定带权有向图G=(V,E),其中每条边的权是非负实数.另外,还给定V中的一个顶点,称之为源.现在要计算从源到其他各顶点的 ...

  4. 图论-单源最短路径算法(拓扑,Dijkstra,Floyd,SPFA)

    前言 单源最短路径是学习图论算法的入门级台阶,但刚开始看的时候就蒙了,什么有环没环,有负权没负权,下面就来总结一下求单源最短路径的所有算法以及其适用的情况. 单源最短路径 设定图中一个点为源点,求其他 ...

  5. 应用单源最短路径算法解决套利交易问题

    目录 导言 基本定义与延伸 基本概念与定义 概念延伸 最短路径算法 Bellman-Ford算法 问题简述 问题分析 问题求解 第一题 第二题 总结 导言 最短路径问题是图论领域中的核心问题之一,也是 ...

  6. 单源路径分支界限java_java单源最短路径算法

    . .. .. . 单源最短路径的 Dijkstra 算法: 问题描述: 给定一... 并 应用贪心法求解单源最短路径问题.环境要求对于环境没有特别要求.对于算法实现,可以自由选择 C, C++, J ...

  7. Dijkstra单源最短路径算法

    这里写目录标题 一.算法原理 二.MATLAB实现 三.参考文献 一.算法原理 迪杰斯特拉算法(Dijkstra)是由荷兰计算机科学家狄克斯特拉于1959 年提出的,因此又叫狄克斯特拉算法.是从一个顶 ...

  8. Dijkstra 单源最短路径算法 Java实现

    Dijkstra 单源最短路径算法 Java实现 算法导入 算法核心 复杂度分析 时间复杂度 空间复杂度 代码实现 参考资料 结尾 算法导入 在图论中,求最短路径有一个经典的算法 Dijkstra算法 ...

  9. 【算法】【ACM】深入理解Dijkstra算法(单源最短路径算法)

    Dijkstra算法是用来求解从某个源点到其他各顶点的最短路径(单源最短路径). 下面的Dijkstra算法的讲解都是基于这个有向图,在遇到其他问题可以类比. 算法的基本思想: 把图中的定点分成两组, ...

最新文章

  1. 你陪我长大 我陪你变老
  2. 并行计算实战-双调排序
  3. 循环序列模型 —— 1.2 数学符号
  4. “评论王争夺赛”活动,第4期开始啦!
  5. java简单小项目_java入门简单小项目有哪些?适合java初学者项目
  6. 高等数理统计(part7)--无偏估计
  7. swagger注解的使用
  8. 笔记本触摸板手势使用
  9. 基于大规模语料的新词发现算法
  10. 手机浏览网页页面缩放
  11. 19.1 快速幂的定义和模板
  12. 题目:请写一段将正整数转化为四进制字符串的函数(十进制正整数转四进制字符串)
  13. 光端机连接示意图详细连接方式图解
  14. 《游戏外挂攻防艺术》学习笔记【一】
  15. CMD快捷指令之启动字符映射表
  16. JVM调优专题-JVM调优参数
  17. 谈我们为什么学不好编程2——你是否已进入“等死模式”?
  18. 品牌商自述:为什么同样的商品,拼多多会更便宜?
  19. [CentOS8+gitlab-ce本地配置手顺]
  20. 程序员硬核“Python抢票教程”,帮你抢回家车票(附源码)

热门文章

  1. ajax常用案例,AJAX实例
  2. 全新折叠旗舰OPPO Find N发布
  3. springboot2.x集成Jetcache遇到的各种问题
  4. AGM AG1280 CPLD
  5. imageJ两张图像融合 我怎么叠加三通道 imageJ叠加
  6. nacos的CP和AP模式
  7. java开发岗位认识_身为一个java开发人员对目前java开发的一些小小的认识
  8. chromium相关问题集合--持续更新中
  9. Multi-Runtime Microservices Architecture
  10. Qt ListView使用