Dijkstra算法
是处理单源最短路径的有效算法,但它局限于边的权值非负的情况,若图中出现权值为负的边,Dijkstra算法就会失效,求出的最短路径就可能是错的。这时候,就需要使用其他的算法来求解最短路径,
Bellman-Ford算法
就是其中最常用的一个。该算法由美国数学家理查德•贝尔曼(Richard Bellman, 动态规划的提出者)和小莱斯特•福特(Lester Ford)发明。Bellman-Ford算法的流程如下:
给定图G(V, E)(其中V、E分别为图G的顶点集与边集),源点s,
-
数组Distant[i]记录从源点s到顶点i的路径长度,初始化数组Distant[n]为, Distant[s]为0;
-
以下操作循环执行至多n-1次,n为顶点数:
对于每一条边e(u, v),如果Distant[u] + w(u, v) < Distant[v],则另Distant[v] = Distant[u]+w(u, v)。w(u, v)为边e(u,v)的权值;
若上述操作没有对Distant进行更新,说明最短路径已经查找完毕,或者部分点不可达,跳出循环。否则执行下次循环;
-
为了检测图中是否存在负环路,即权值之和小于0的环路。对于每一条边e(u, v),如果存在Distant[u] + w(u, v) < Distant[v]的边,则图中存在负环路,即是说改图无法求出单源最短路径。否则数组Distant[n]中记录的就是源点s到各顶点的最短路径长度。
可知,
Bellman-Ford算法
寻找单源最短路径的时间复杂度为O(V*E).
首先介绍一下松弛计算。如下图:
松弛计算之前,点B的值是8,但是点A的值加上边上的权重2,得到5,比点B的值(8)小,所以,点B的值减小为5。这个过程的意义是,找到了一条通向B点更短的路线,且该路线是先经过点A,然后通过权重为2的边,到达点B。
当然,如果出现一下情况
则不会修改点B的值,因为3+4>6。
Bellman-Ford算法可以大致分为三个部分
第一,初始化所有点。每一个点保存一个值,表示从原点到达这个点的距离,将原点的值设为0,其它的点的值设为无穷大(表示不可达)。
第二,进行循环,循环下标为从1到n-1(n等于图中点的个数)。在循环内部,遍历所有的边,进行松弛计算。
第三,遍历途中所有的边(edge(u,v)),判断是否存在这样情况:
d(v) > d (u) + w(u,v)
则返回false,表示途中存在从源点可达的权为负的回路。
之所以需要第三部分的原因,是因为,如果存在从源点可达的权为负的回路。则 应为无法收敛而导致不能求出最短路径。
考虑如下的图:
经过第一次遍历后,点B的值变为5,点C的值变为8,这时,注意权重为-10的边,这条边的存在,导致点A的值变为-2。(8+ -10=-2)
第二次遍历后,点B的值变为3,点C变为6,点A变为-4。正是因为有一条负边在回路中,导致每次遍历后,各个点的值不断变小。
在回过来看一下bellman-ford算法的第三部分,遍历所有边,检查是否存在d(v) > d (u) + w(u,v)。因为第二部分循环的次数是定长的,所以如果存在无法收敛的情况,则肯定能够在第三部分中检查出来。比如
此时,点A的值为-2,点B的值为5,边AB的权重为5,5 > -2 + 5. 检查出来这条边没有收敛。
所以,Bellman-Ford算法可以解决图中有权为负数的边的单源最短路径问。
个人感觉算法导论讲解很不错,把这一章贴出来和大家分享:
24.1 The Bellman-Ford algorithm
The
Bellman-Ford algorithm
solves the single-source shortest-paths problem in the general case in which edge weights may be negative. Given a weighted, directed graph
G
= (
V
,
E
) with source
s
and weight function
w
:
E
→
R
, the Bellman-Ford algorithm returns a boolean value indicating whether or not there is a negative-weight cycle that is reachable from the source. If there is such a cycle, the algorithm indicates that no solution exists. If there is no such cycle, the algorithm produces the shortest paths and their weights.
The algorithm uses relaxation, progressively decreasing an estimate
d
[
v
] on the weight of a shortest path from the source
s
to each vertex
v
∈
V
until it achieves the actual shortest-path weight
δ
(
s
,
v
). The algorithm returns TRUE if and only if the graph contains no negative-weight cycles that are reachable from the source.
BELLMAN-FORD(G, w, s) 1 INITIALIZE-SINGLE-SOURCE(G, s) 2 for i ← 1 to |V[G]| - 1 3 do for each edge (u, v) ∈ E[G] 4 do RELAX(u, v, w) 5 for each edge (u, v) ∈ E[G] 6 do if d[v] > d[u] + w(u, v) 7 then return FALSE 8 return TRUE
Figure 24.4
shows the execution of the Bellman-Ford algorithm on a graph with 5 vertices. After initializing the
d
and π values of all vertices in line 1, the algorithm makes |
V
| – 1 passes over the edges of the graph. Each pass is one iteration of the
for
loop of lines 2-4 and consists of relaxing each edge of the graph once. Figures 24.4(b)-(e) show the state of the algorithm after each of the four passes over the edges. After making |
V
|- 1 passes, lines 5-8 check for a negative-weight cycle and return the appropriate boolean value. (We’ll see a little later why this check works.)
Figure 24.4: The execution of the Bellman-Ford algorithm. The source is vertex
s
. The
d
values are shown within the vertices, and shaded edges indicate predecessor values: if edge (
u, v
) is shaded, then π[
v
] =
u
. In this particular example, each pass relaxes the edges in the order (
t, x
), (
t, y
), (
t, z
), (
x, t
), (
y, x
), (
y, z
), (
z, x
), (
z, s
), (
s, t
), (
s, y
). (a) The situation just before the first pass over the edges. (b)-(e) The situation after each successive pass over the edges. The
d
and π values in part (e) are the final values. The Bellman-Ford algorithm returns TRUE in this example.
The Bellman-Ford algorithm runs in time
O
(
V E
), since the initialization in line 1 takes Θ(
V
) time, each of the |
V
| – 1 passes over the edges in lines 2-4 takes Θ(
E
) time, and the
for
loop of lines 5-7 takes
O
(
E
) time.
以下是
Bellman-Ford代码
:
/*
* About: Bellman-Ford算法
* Author: Tanky Woo
* Blog: www.WuTianqi.com
*/
#include
<
iostream
>
using
namespace
std;
const
int
maxnum
=
100
;
const
int
maxint
=
99999
;
//
边,
typedef
struct
Edge{
int
u, v;
//
起点,重点
int
weight;
//
边的权值
}Edge;
Edge edge[maxnum];
//
保存边的值
int
dist[maxnum];
//
结点到源点最小距离
int
nodenum, edgenum, source;
//
结点数,边数,源点
//
初始化图
void
init()
{
//
输入结点数,边数,源点
cin
>>
nodenum
>>
edgenum
>>
source;
for
(
int
i
=
1
; i
<=
nodenum;
++
i)
dist[i]
=
maxint;
dist[source]
=
0
;
for
(
int
i
=
1
; i
<=
edgenum;
++
i)
{
cin
>>
edge[i].u
>>
edge[i].v
>>
edge[i].weight;
if
(edge[i].u
==
source)
//
注意这里设置初始情况
dist[edge[i].v]
=
edge[i].weight;
}
}
//
松弛计算
void
relax(
int
u,
int
v,
int
weight)
{
if
(dist[v]
>
dist[u]
+
weight)
dist[v]
=
dist[u]
+
weight;
}
bool
Bellman_Ford()
{
for
(
int
i
=
1
; i
<=
nodenum
–
1
;
++
i)
for
(
int
j
=
1
; j
<=
edgenum;
++
j)
relax(edge[j].u, edge[j].v, edge[j].weight);
bool
flag
=
1
;
//
判断是否有负环路
for
(
int
i
=
1
; i
<=
edgenum;
++
i)
if
(dist[edge[i].v]
>
dist[edge[i].u]
+
edge[i].weight)
{
flag
=
0
;
break
;
}
return
flag;
}
int
main()
{
//
freopen(“input3.txt”, “r”, stdin);
init();
if
(Bellman_Ford())
for
(
int
i
=
1
;i
<=
nodenum; i
++
)
cout
<<
dist[i]
<<
endl;
return
0
;
}
Dijkstra(迪杰斯特拉)算法是典型的最短路径路由算法,用于计算一个节点到其他所有节点的最短路径。主要特点是以起始点为中心向外层层扩展,直到扩展到终点为止。
Dijkstra算法
能得出最短路径的最优解,但由于它遍历计算的节点很多,所以效率低。
Dijkstra算法是很有代表性的最短路算法,在很多专业课程中都作为基本内容有详细的介绍,如数据结构,图论,运筹学等等。
其基本思想是,设置顶点集合S并不断地作贪心选择来扩充这个集合。一个顶点属于集合S当且仅当从源到该顶点的最短路径长度已知。
初始时,S中仅含有源。设u是G的某一个顶点,把从源到u且中间只经过S中顶点的路称为从源到u的
特殊路径
,并用数组dist记录当前每个顶点所对应的最短特殊路径长度。Dijkstra算法每次从V-S中取出具有最短特殊路长度的顶点u,将u添加到S中,同时对数组dist作必要的修改。一旦S包含了所有V中顶点,dist就记录了从源到所有其它顶点之间的最短路径长度。
例如,对下图中的有向图,应用
Dijkstra算法
计算从源顶点1到其它顶点间最短路径的过程列在下表中。
主题好好理解上图!
以下是具体的实现(C/C++):
Floyd-Warshall算法,简称
Floyd算法
,用于
求解任意两点间的最短距离,时间复杂度为O(n^3)。
使用条件&范围
通常可以在任何图中使用,包括有向图、带负权边的图。
Floyd-Warshall 算法用来找出每对点之间的最短距离。它需要用邻接矩阵来储存边,这个算法通过考虑最佳子路径来得到最佳路径。
1.注意单独一条边的路径也不一定是最佳路径。
2.从任意一条单边路径开始。所有两点之间的距离是边的权,或者无穷大,如果两点之间没有边相连。
对于每一对顶点 u 和 v,看看是否存在一个顶点 w 使得从 u 到 w 再到 v 比己知的路径更短。如果是更新它。
3.不可思议的是,只要按排适当,就能得到结果。
伪代码:
我们平时所见的
Floyd算法的一般形式
如下:
注意下第6行这个地方,如果dist[i][k]或者dist[k][j]不存在,程序中用一个很大的数代替。最好写成if(dist[i] [k]!=INF && dist[k][j]!=INF && dist[i][k]+dist[k][j]<dist[i][j]),从而防止溢出所造成的错误。 floyd算法的实现以及输出最短路径和最短路径长度,具体过程请看【
动画演示Floyd算法】。
代码说明几点:
1、A[][]数组初始化为各顶点间的原本距离,最后存储各顶点间的最短距离。
2、path[][]数组保存最短路径,与当前迭代的次数有关。初始化都为-1,表示没有中间顶点。在求A[i][j]过程中,path[i][j]存放从顶点vi到顶点vj的中间顶点编号不大于k的最短路径上前一个结点的编号。在算法结束时,由二维数组path的值回溯,可以得到从顶点vi到顶点vj的最短路径。
初始化A[][]数组为如下,即有向图的邻接矩阵。
完整的
实现代码
如下: