网络最短路——Floyd算法实现

Floyd算法(Floyd-Warshall算法)是一种用于求解图中所有顶点对之间最短路径的算法,该算法名称以创始人之一、1978年图灵奖获得者、斯坦福大学计算机科学系教授罗伯特·弗洛伊德命名。Floyd算法可以应用于许多方面,特别是在交通、物流和通信网络的优化中,譬如城市交通规划:Floyd算法可以帮助规划城市交通网络,确定最短路径和最优路线,以减少交通拥堵和行程时间。物流管理:在物流领域,Floyd算法可以用于确定不同地点之间的最短路径,帮助优化货物运输和配送的路线,降低成本并提高效率。电信网络规划:Floyd算法可应用于电信网络中的路由优化,帮助确定数据包传输的最短路径,提高网络连接的速度和质量。航班路径规划:航空公司可以使用Floyd算法确定不同机场之间的最短路径,优化航班计划和航线,提高飞行效率和旅客体验。金融交易网络:在金融领域,Floyd算法可以用于分析银行间的交易网络,确定最短路径和关键参与者,以便监测风险和进行迅速的资金清算,等等。

一、最短路问题

若网络中的每条边都有一个数值(长度、成本、时间等),则找出两节点(通常是源节点和阱节点)之间总权和最小的路径就是最短路问题。最通用的最短路问题(认为是标准型)可以如此描述:希望在网络中找到一条从源节点(source node)到接收节点(target node)的最小成本路径,这里的最小成本可定义为路径长度、旅行时间、旅行费用等。
\(G\)是由节点集合\(V\)(元素个数为n)和弧集合\(A\)(元素个数为m)组成的网络,定义节点\(s ∈ V\)为源节点(source),其他节点为非源节点(non-source),路径长度为该路径所包含弧的长度之和。求解单源最短路径问题就是找出源节点\(s\)到每一个非源节点\(t\)的有向最短路径。最短路径问题的数学模型如下:

\[\begin{gathered} \text { Minimize }\sum_{(i, j) \in A} c_{i j} x_{i j} \\ \text { s.t. } \quad \sum_{\{j:(i, j) \in A\}} x_{i j}-\sum_{\{j:(j, i) \in A\}} x_{j i}= \begin{cases}1, & \text { if } i=s \\ -1, & \text { if } i=t \\ 0, & \text { otherwise }\end{cases} \\ x_{i j}: 0-1 \text { 决策变量, } x_{i j}=1 \text { 表示经过弧 }(i, j), x_{i j}=0 \text { 表示不经过弧 }(i, j) . \end{gathered} \]

二、最短路的Floyd算法

求图上两点\(i,j\) 之间的最短距离,可以按“从小图到全图”的步骤,在逐步扩大图的过程中计算和更新最短路。想象图中的每个点是一个灯,开始时所有灯都是灭的。然后逐个点亮灯,每点亮一个灯,就重新计算\(i,j\) 的最短路,要求路径只能经过点亮的灯。所有灯都点亮后计算结束。在这个过程中,点亮第\(k\)个灯时,能用到\(1∼k−1\)个亮灯的结果。

Floyd算法是经典的动态规划算法,基本思想是递推产生一个矩阵序列\(A_1,A_2,..,A_k,...,A_n\)(图有\(n\)个节点),\(A_k=[a_k(i,j)]_{n \times n}\)。其中矩阵\(A_k\)\(i\)行第\(j\)列表示从顶点\(v_i\)到顶点\(v_j\)的路径上经过的顶点序号不大于\(k\)的最短路径长度。
迭代公式\(a_{k}(i,j)=min(a_{k-1}(i,j),a_{k-1}(i,k)+a_{k-1}(k,j))]\)
\(k\)是迭代次数,\(i,j,k=1,2,...,n\)
当最后\(k=n\)是时,\(A_n\)矩阵就是各个顶点之间的最短距离值了。
那么如果需要记录路径,那么需要引入路由矩阵。路由矩阵\(R_k=[r_k(i,j)]_{n \times n}\),用来记录两点之间路径的前驱后继的关系,其中\(r_k(i,j)\)表示从顶点\(v_i\)到顶点\(v_j\)的路径经过编号为\(r_k(i,j)\)的顶点。所以需要注意了,路由矩阵里面不是数值,而是结点。

以上理论听起来挺复杂的。不过没有关系的。真正的算法实现无外乎就是三个循环嵌套,\(i,j\)的循环是任意两个点,而\(k\)则是两个点之间所经过的第三个点,我们就是在循环之中不断比较从\(i\)\(j\)的距离与从\(i\)\(k\)距离加上从\(k\)\(j\)距离的大小,如果经过这个点,路径变短了,我们就接受这个点,认为可以经过这个点;否则就不经过这个点,就是从\(i\)\(j\)最短。
如果我们得到了路由矩阵,那怎样得到路径呢?我们得到的路由矩阵\(R_n\),其中\(r(i,j)\)(就是路由矩阵第\(i\)\(j\)列)表示第\(i\)个节点到第\(j\)个节点最短路径经过的中间点\(p_1\)。那么我们先向起始顶点\(v_i\)反向追踪,得到\(r(i,p_1)=p_2,r(i,p_2)=p_3...,\)最终\(r(i,p_n)=0\),就结束了;再正向追踪\(r(p_1,j)=q_1,r(q_1,j)=q_2.....r(q_t,j)=0\),完成正向追踪。路径就是\(v_i,p_n,...,p_2,p_1,q_1,q_2,...,q_t,v_j\)

三、最短路Python求解

案例:考虑下图程序所作图的最短路。

3.1 作网络图

import numpy as np
import networkx as nx
import matplotlib.pyplot as plt

A = np.mat([[0, 3, 0, 2, 0],
            [3, 0, 2, 1, 0],
            [0, 2, 0, 0, 5],
            [2, 1, 0, 0, 4],
            [0, 0, 5, 4, 0]])  # Adjacency matrix

G = nx.Graph(A)  # Undirected weighted graph
pos = nx.shell_layout(G)  # Set the layout for node positions
w = nx.get_edge_attributes(G, "weight")  # Get the edge weights

nx.draw_networkx(G=G, pos=pos, node_size=260)  # Draw the network graph
nx.draw_networkx_edge_labels(G=G, pos=pos, font_size=12, edge_labels=w)  # Label the edge weights
plt.show()

3.2 最短路计算

import numpy as np
from numpy import inf

A = [[inf, 3, inf, 2, inf],
     [3, inf, 2, 1, inf],
     [inf, 2, inf, inf, 5],
     [2, 1, inf, inf, 4],
     [inf, inf, 5, 4, inf]]

n = len(A)  # Calculate the number of vertices in the graph.

dis = A  # Initialize the dis matrix with the given adjacency matrix A.
path = np.zeros((n, n))  # Initialize the path matrix with zeros.

for k in range(n):
    for i in range(n):
        for j in range(n):
            if dis[i][k] + dis[k][j] < dis[i][j]:
                dis[i][j] = dis[i][k] + dis[k][j]  # Update the shortest distance between i and j through k.
                path[i][j] = k  # Record the intermediate vertex k in the shortest path between i and j.

print("Shortest path distances (dis):")
print(np.array(dis))  # Display the matrix containing the shortest path distances between all pairs of vertices.

print("Intermediate vertices in shortest paths (path):")
print(np.array(path, dtype=int))  # Display the matrix representing the intermediate vertices in the shortest paths.
Shortest path distances (dis):
[[4 3 5 2 6]
 [3 2 2 1 5]
 [5 2 4 3 5]
 [2 1 3 2 4]
 [6 5 5 4 8]]
Intermediate vertices in shortest paths (path):
[[3 0 1 0 3]
 [0 3 0 0 3]
 [1 0 1 1 0]
 [0 0 1 1 0]
 [3 3 0 0 3]]

我们以0到4 点为例,最短距离是6。path[0][4]=3,该路径经过点3,反向追踪:path[0][3]=0,结束;正向追踪:path[3][4]=0,结束。最短路径就是0->3->4。看一看前面的图,的确符合事实。但是,实际上,这五个点最好用a,b,c,d,e等表示,或是1,2,3,4,5。这里的用做终止追踪的0其实是初始时的0,而这个矩阵当中也有作为节点标记的0,看的时候很容易混淆。如果我们用字母标记,最终会看到路由矩阵中有节点标记,也有数字0,看起来会方便很多。

四、最短路R求解

实例:求下图所示网络各顶点间的最短路。

4.1 作网络图

library(igraph)
data <- matrix(c(
  0, 30, 15, 0, 0, 0,
  5, 0, 0, 0, 20, 30,
  0, 10, 0, 0, 0, 15,
  0, 0, 0, 0, 0, 0,
  0, 0, 0, 10, 0, 0,
  0, 0, 0, 30, 10, 0
), nrow = 6, byrow = TRUE)

rownames(data) <- c("S1", "S2", "s3", "s4", "s5", "s6")
colnames(data) <- c("S1", "S2", "s3", "s4", "s5", "s6")

# Create the graph
graph <- graph.adjacency(data, mode = "directed", weighted = TRUE, diag = FALSE)
# Plot the graph with row names as labels
plot(graph, vertex.label = rownames(data), vertex.size = 30, vertex.color = "lightblue", edge.label = E(graph)$weight)

4.2 最短路计算

# 准备数据
data <- matrix(c(
  0, 30, 15, Inf, Inf, Inf,
  5, 0, Inf, Inf, 20, 30,
  Inf, 10, 0, Inf, Inf, 15,
  Inf, Inf, Inf, 0, Inf, Inf,
  Inf, Inf, Inf, 10, 0, Inf,
  Inf, Inf, Inf, 30, 10, 0
), nrow = 6, byrow = TRUE)

path <- matrix(0, nrow = 6, ncol = 6)

for (k in 1:6) {
  for (i in 1:6) {
    for (j in 1:6) {
      if (data[i, j] > data[i, k] + data[k, j]) {
        data[i, j] <- data[i, k] + data[k, j]
        path[i, j] <- k
      }
    }
  }
}

# 定义函数找出x到y的具体路径
show_trace <- function(x, y) {
  trace <- c()
  add_trace <- function(x, y) {
    if (path[x, y] != 0) {
      add_trace(x, path[x, y])
      trace <<- c(trace, path[x, y])
      add_trace(path[x, y], y)
    }
  }
  add_trace(x, y)
  if (length(trace) > 0) {
    trace_str <- paste(c(x, trace, y), collapse = "-->")
    cat(paste("从", x, "到", y, "的最短路径为:", trace_str))
  } else {
    cat(paste("从", x, "到", y, "不存在路径"))
  }
}

print(data)
show_trace(1, 5)  # 求S1到S5的最短路径
show_trace(1, 4)  # 求S1到S4的最短路径
     [,1] [,2] [,3] [,4] [,5] [,6]
[1,]    0   25   15   50   40   30
[2,]    5    0   20   30   20   30
[3,]   15   10    0   35   25   15
[4,]  Inf  Inf  Inf    0  Inf  Inf
[5,]  Inf  Inf  Inf   10    0  Inf
[6,]  Inf  Inf  Inf   20   10    0
从 1 到 5 的最短路径为: [1-->3-->6-->5]
从 1 到 4 的最短路径为: [1-->3-->6-->5--> 4]

总结

弗洛伊德算法的算法实现比较简单,过程容易理解,但原理不是很容易消化透彻。同学们在学习的时候可以根据算法过程,找一个具体的图仔细捋一捋,画一下距离矩阵与路由矩阵变化过程,以及当中第三点的选取、距离的比较等,体会当中的动态规划思想。该算法可以求最短路径及其长度,但是对于复杂的网络,该算法还是有局限性的。这个时候我们便需要选择其它的自然算法,例如蚁群算法等来进行求解。虽然那些相对“高级”的算法不一定在众多的路径中找到最短的那一条,但是却可以在较短时间内找到比较短的一条路径,而且会很接近最短路径的。

参考文献

  1. 最短路问题与标号算法(label correcting algorithm)研究(2)
  2. Floyd算法
  3. 弗洛伊德(Floyd)算法详解
posted @ 2023-06-27 17:42  郝hai  阅读(378)  评论(0编辑  收藏  举报