前言
在最短路径问题中,约定图是一个带权值的有向图。最短路径是解决两节点之间的最小代价问题。最短路径有几种分类:单源最短路径;单目标最短路径;单节点对最短路径;所有节点对最短路径。下面记录单源最短路径问题。
单源最短路径
定义:
单源最短路径是给定一个图,希望从一个源节点
到每个节点
的最短路径。
单源最短路径的算法
在介绍算法之前,首先必须介绍下松弛操作,在整个算法中,松弛操作是很重要的,松弛操作的目的是要获得最短路径估计;在任何操作之前必须对节点进行初始化操作,松弛过程:将节点
到节点
之间的最短路径距离加上节点
到节点
之间的边权值,并与当前节点
到节点
的最短路径估计进行比较,如果前者较小,则更新最短路径估计
和前驱
属性。
//初始化操作
INITIALIZE-SINGLE-SOURCE(G,s)
for each vertex v ∈ G.V
v.d = inf
v.front = NIL
s.d = 0
//松弛操作
RELAX(u,v,w) //w是权值函数
if v.d > u.d + w(u,v)
v.d = u.d + w(u,v)
v.front = u
松弛操作过程结构:
根据对图的边进行不同松弛次数和次序,解决单源最短路径问题,下面介绍两种算法:Bellman-Ford算法和Dijkstra算法。
Bellman-Ford算法
该算法适用于边权值可为负,但不能有权值为负带有环路的图(
因为如果包含环路,且环路的权值和为正的,那么去掉这个环路,可以得到更短的路径;如果环路的权值是负的,那么肯定没有解)
。该算法是对每一条边进行次松弛,通过对边的松弛操作渐近地降低从源节点
到每个节点
的最短路径估计值
,直到该估计值与实际的最短路径权重
相同为止。若该算法的输入图不包含可以从源节点到达的权重为负值的环路,则返回TRUE。
步骤:
//Bellman-Ford算法
BELLMAN-FORD(G,w,s)
INITIALIZE-SINGLE-SOURCE(G,s)
for i = 1 to |G.V|-1
for each edge (u,v) ∈ G.E
RELAX(u,v,w)
for each edge (u,v) ∈ G.E
if v.d > u.d + w(u,v)
return FALSE
return TRUE
procedure BellmanFord(list vertices, list edges, vertex source)
// 该实现读入边和节点的列表,并向两个数组(distance和predecessor)中写入最短路径信息
// 步骤1:初始化图
for each vertex v in vertices:
if v is source then distance[v] := 0
else distance[v] := infinity
predecessor[v] := null
// 步骤2:重复对每一条边进行松弛操作
for i from 1 to size(vertices)-1:
for each edge (u, v) with weight w in edges:
if distance[u] + w < distance[v]:
distance[v] := distance[u] + w
predecessor[v] := u
// 步骤3:检查负权环
for each edge (u, v) with weight w in edges:
if distance[u] + w < distance[v]:
error "图包含了负权环"
举例:算法执行过程中,源节点为
,图a是初始化后的结果。每一次松弛操作对边的处理次序都是:
,操作的时候记住要按照顺序进行松弛,例如从图a到图b最短路径估计值的变化过程是:第二行:表示初始化结果;第三行:表示按顺序执行完
后的结果;第四行:表示执行完
的结果;
s | t | x | y | z |
0 | ∞ | ∞ | ∞ | ∞ |
0 | 6 | ∞ | ∞ | ∞ |
0 | 6 | ∞ | 7 | ∞ |
Dijkstra算法
Dijkstra
算法要求所有边的权重都为非负。该算法在执行的过程中维持的关键信息是一组节点集合,从源节点
到该集合
的每个节点之间都是最短路径已被找到。算法重复从节点集合
中选择最短路径估计最小的节点
,将节点
加入到集合
,然后对从节点
发出的边进行松弛。
//Dijkstra算法
Dijkstra(G,w,s)
INITIALIZE-SINGLE-SOURCE(G,s)
S = ∅
Q = G.V
while Q ≠ ∅
u = EXTRACT-MIN(Q)
S = S ∪{u}
for each vertex v∈G.Adj[u]
RELAX(u,v,w)
举例:执行过程中,刚开始集合为空,则把集合
中最短路径估计最小的节点
加入到集合
,
然后对从节点
发出的边进行松弛。
第一步(图b):将节点加入集合
,并对节点
所发出的边进行松弛操作;
第二步(图c):在集合中最短路径估计最小的节点为
,将节点
加入到集合
,并对节点
所发出的边进行松弛操作;
第三步(图d):在集合中最短路径估计最小的节点为
,将节点
加入到集合
,并对节点
所发出的边进行松弛操作;
第四步(图e):在集合中最短路径估计最小的节点为
,将节点
加入到集合
,并对节点
所发出的边进行松弛操作;
第五步(图f):在集合中最短路径估计最小的节点为
,将节点
加入到集合
,并对节点
所发出的边进行松弛操作;