最短路径问题是图论研究中的一个经典算法问题, 旨在寻找图(由结点和路径组成的)中两结点之间的最短路径。 算法具体的形式包括:
- 确定起点的最短路径问题 - 即已知起始结点,求起始点到所有点的最短路径的问题。
- 确定终点的最短路径问题 - 与确定起点的问题相反,该问题是已知终结结点,求最短路径的问题。在无向图中该问题与确定起点的问题完全等同,有向图中该问题等同于把所有路径方向反转的确定起点的问题。
- 确定起点终点的最短路径问题 - 即已知起点和终点,求两结点之间的最短路径。
- 全局最短路径问题 - 求图中所有的最短路径。
上面引自百度百科,最短路径的含义很好理解,下面我们直接看一下问题解法。
Dijkstra 迪杰斯特拉
迪杰斯特拉算法,计算的是一个点到其余所有点的最短路径,或者说叫单源路径问题,对应于上述前三类问题。算法也是满满的贪心和动态规划的思路。
算法基本思想:
如果点 i 到点 j 的最短路径经过k,则ij路径中,i到k的那一段一定是i到k的最短路径。
核心思路:
(1)用3个一维数组(长度为图中顶点个数):一个用来标识该顶点是否已经找到最短路径,一个数组short_path用来记录初始点到该点的最短路径,还有一个记录最短路径情况下该点的前一个顶点是什么。
(2)求解:计算v0到vk的最短路径时,比较当前short_path[k]的值与 short_path[i] + vi-vk的距离相当于看一下绕路会不会更近。
Floyd 弗洛伊德算法
弗洛伊德与迪杰斯特拉的区别,就是弗洛伊德算法用于求所有点到所有点的最短距离。该算法非常优雅,我们学习一个。
算法核心思路其实和迪杰斯特拉一致,由于要求所有点到所有点,算法需要三次循环遍历,迪杰斯特拉记录路径的一维数组变成了二维数组。
贝尔曼-福特算法(Bellman-Ford)
贝尔曼-福特算法是由理查德·贝尔曼(Richard Bellman) 和 莱斯特·福特 创立的,求解单源最短路径问题的一种算法。有时候这种算法也被称为 Moore-Bellman-Ford 算法,因为 Edward F. Moore 也为这个算法的发展做出了贡献。它的原理是对图进行V-1次松弛操作,得到所有可能的最短路径。其优于迪杰斯特拉的方面是边的权值可以为负数、实现简单,缺点是时间复杂度过高,高达O(VE)。
仍然以此图为例,我们演示一下迪杰斯特拉和佛洛依德算法。
已知条件还是顶点数和边:
n = 9
edge = [(0,1,10), (0,5,11), (1,2,18),(1,6,16), (1,8,12), (2,3,22),(2,8,8), (3,4,20), (3,6,24), (3,7,16), (3,8,21), (4,5,26), (4,7,7), (5,6,17), (6,7,19)]
def shortestPath_Dijkstra(n, edge, begin, end):
from collections import defaultdict
# 根据边 建图
graph = defaultdict(dict)
for e in edge:
graph[e[0]][e[1]] = e[2]
graph[e[1]][e[0]] = e[2]
# 初始化关键变量
short_path = [0 for i in range(n)]
prenode = [0 for i in range(n)]
visit = [0 for i in range(n)]
for i in range(n):
short_path[i] = graph[begin].get(i, 999)
short_path[begin] = 0
visit[begin] = 1
# 核心部分
for i in range(n-1):
minw = 999
node = begin
# 找当前最近点
for j in range(n):
if not visit[j] and short_path[j] < minw:
node = j
minw = short_path[j]
if node == end:
break
visit[node] = 1
short_path[node] = 0
# 更新short_path
for k in range(n):
if not visit[k] and (short_path[k] > minw + graph[node].get(k, 999)):
short_path[k] = minw + graph[node].get(k, 999)
prenode[k] = node
if node == end:
break
route = [str(end)]
x = end
while x != begin:
x = prenode[x]
route.append(str(x))
return short_path[end], "-".join(route[::-1])
注:如果求起始点到所有点的距离,把end相关内容去掉。
代码运行结果如下
shortestPath_Dijkstra(n, edge, 0, 4)
# (37, '0-5-4')
shortestPath_Dijkstra(n, edge, 0, 7)
# (44, '0-5-4-7')
def shortPath_Floyd(n, edge):
from collections import defaultdict
import numpy as np
# 根据边 建图
graph = defaultdict(dict)
for e in edge:
graph[e[0]][e[1]] = e[2]
graph[e[1]][e[0]] = e[2]
# 初始化关键变量
short_path = np.zeros((n, n))
prenode = np.zeros((n, n))
for i in range(n):
for j in range(n):
short_path[i, j] = graph[i].get(j, 999)
prenode[i, j] = j
print("初始状态:")
print(short_path)
print(prenode)
for i in range(n):
for j in range(n):
for k in range(n):
if short_path[j, k] > short_path[j, i] + short_path[i, k]:
short_path[j ,k] = short_path[j, i] + short_path[i, k]
prenode[j ,k] = prenode[j ,i]
print("最终结果:")
print(short_path)
print(prenode)
注意:该算法是求解所有点到所有点,所以最后结果是在两个矩阵中。
shortPath_Floyd(n, edge)
'''
初始状态:
[[999. 10. 999. 999. 999. 11. 999. 999. 999.]
[ 10. 999. 18. 999. 999. 999. 16. 999. 12.]
[999. 18. 999. 22. 999. 999. 999. 999. 8.]
[999. 999. 22. 999. 20. 999. 24. 16. 21.]
[999. 999. 999. 20. 999. 26. 999. 7. 999.]
[ 11. 999. 999. 999. 26. 999. 17. 999. 999.]
[999. 16. 999. 24. 999. 17. 999. 19. 999.]
[999. 999. 999. 16. 7. 999. 19. 999. 999.]
[999. 12. 8. 21. 999. 999. 999. 999. 999.]]
[[0. 1. 2. 3. 4. 5. 6. 7. 8.]
[0. 1. 2. 3. 4. 5. 6. 7. 8.]
[0. 1. 2. 3. 4. 5. 6. 7. 8.]
[0. 1. 2. 3. 4. 5. 6. 7. 8.]
[0. 1. 2. 3. 4. 5. 6. 7. 8.]
[0. 1. 2. 3. 4. 5. 6. 7. 8.]
[0. 1. 2. 3. 4. 5. 6. 7. 8.]
[0. 1. 2. 3. 4. 5. 6. 7. 8.]
[0. 1. 2. 3. 4. 5. 6. 7. 8.]]
最终结果:
[[20. 10. 28. 43. 37. 11. 26. 44. 22.]
[10. 20. 18. 33. 42. 21. 16. 35. 12.]
[28. 18. 16. 22. 42. 39. 34. 38. 8.]
[43. 33. 22. 32. 20. 41. 24. 16. 21.]
[37. 42. 42. 20. 14. 26. 26. 7. 41.]
[11. 21. 39. 41. 26. 22. 17. 33. 33.]
[26. 16. 34. 24. 26. 17. 32. 19. 28.]
[44. 35. 38. 16. 7. 33. 19. 14. 37.]
[22. 12. 8. 21. 41. 33. 28. 37. 16.]]
[[1. 1. 1. 1. 5. 5. 1. 5. 1.]
[0. 0. 2. 8. 6. 0. 6. 6. 8.]
[1. 1. 8. 3. 3. 1. 1. 3. 8.]
[8. 8. 2. 7. 4. 6. 6. 7. 8.]
[5. 7. 3. 3. 7. 5. 7. 7. 3.]
[0. 0. 0. 6. 4. 0. 6. 4. 0.]
[1. 1. 1. 3. 7. 5. 1. 7. 1.]
[4. 6. 3. 3. 4. 4. 6. 4. 3.]
[1. 1. 2. 3. 3. 1. 1. 3. 2.]]
'''
Leetcode题目 —— 743. Network Delay Time
You are given a network of n
nodes, labeled from 1
to n
. You are also given times
, a list of travel times as directed edges times[i] = (ui, vi, wi)
, where ui
is the source node, vi
is the target node, and wi
is the time it takes for a signal to travel from source to target.
We will send a signal from a given node k
. Return the time it takes for all the n
nodes to receive the signal. If it is impossible for all the n
nodes to receive the signal, return -1
.
题解:
这道题是有向图中的最短路径问题,求点k到其他所有点的最短距离,取其极大值,乃网络的延迟时间。用上面的Dijkstra算法套用即可不再列出代码,多练习发现自己理解上的偏差。