其数学表现形式为:
在最短路径问题中,我们给定一个带权重的有向图G=(V, E) 和权重函数 ω:E→R , 该权重函数将每条边映射到实数值的权重上,图中一条路径 p=<v0,v1,...,vk> 的权重 ω(p) 是构成该路径的所有边的权重之和:
δ(p)={min{ω(p):u∼v},∞,如果存在一条从节点u到节点v的路径其他
从节点 u 到节点 v 的最短路径则定义为任何一条权重 ω(p)=δ(u,v) 的路径p。
最短路径可分为单源最短路径问题和多源最短路径问题。
1. 单源最短路径问题: 给定一个图 G=(V,E) , 我们希望找到从给源节点 s∈V 到每个节点 v∈V 的最短路径。
2. 多源最短路径问题 : 求任意两个节点的最短路径。
注意:这里讨论的算法都假设,不存在权重为负值的边并且不存在环路
最短路径算法通常依赖最短路径的一个重要的性质:两个节点之间的一条最短路径包含着其他的最短路径。
对于无权图来说,我们可以使用广度优先搜索算来进行求解。但是我们需要对BFS算法进行修改。因为,我们需要计算源点到任何节点的最短路径,所以我们需要一个数组dist用来保存最短路径的距离和使用path数组用来保存路径。
及dist[W] = S 到W的最短距离。则有dis[s] = 0 。
path[W] = S到W进过的某个节点。
伪代码为:
void shortestPath(Vertex s) {
dist[|V|] = -1;//首先对数组进行初始化
path[|V|]; //定义一个path数组
queue Q; //创建一个队列
Q.push(s); //将源点插入到队列中
while (!Q.empty()) { //如果队列不为空
v = Q.front(); Q.pop(); //出队列
for (v的每个邻接点 w) {
if (dist[w] == -1) { //如果等于-1表明,我们没有计算该节点
dist[w] = dist[v] + 1;
path[w] = v; //表示s到w的路径经过了v
Q.push(w); //将w压入队列
}
} // for
} // while
}
其时间复杂度为 O(|V|+|E|) 。
Dijkstra算法解决的是带权重图上单源最短路径问题,该算法要求所有的边的权重都为非负值。Dijkstra算法在运行过程中维持的关键信息是一组节点集合 S 。从源节点 s 到该集合中每个节点之间的最短路径已经被找到。算法重复从节点集 V−S 中选择最短路径估计最小的节点 u , 将 u 加入到集合 S ,然后对所有从 u 发出的边进行松弛。
其伪代码表示为:
void Dijkstra(Vertex s) {
while (1) {
v = 未收录顶点中dist最小者;
if (这样的v不存在) break; //表明所有的节点都已经收录了
collocted[v] = true; //收录该节点
for (v 的每个邻接点 w) {
if (collected[w] == false) {
if (dist[v] + E < dist[w]) {
dist[w] = dist[v] + E ;
path[w] = v;
}
}
} // for
}
}
在上述代码中v = 未收录顶点中dist最小者;
对于不同的存储方式其算法的复杂度是不一样的。
Dijkstra算法的“栗子”请看 07-图6 旅游规划 (25分)
多源最短路径问题:给定一个图,求任意两个节点之间的最短路径。
对于方法1直接调用单源最短路径算法,因为,我们前面已经介绍了单源最短路径算法。因此,这里将不再继续讨论。
现在我们重点来讨论一下Floyd-Warshall算法。
最短路径的结构
假定图 G 的所有节点为 V=1,2,...,n ,考虑其中的一个子集 1,2,...,k ,这里 k 是某个小于 n 的整数。对于任意节点对 i,j∈V , 考虑从节点 i 到节点 j 的所有中间节点均取自集合 1,2,...,k 的路径,并且设 p 为其中权重最小的路径(路径 p 是简单路径)。Floyd-Warshall算法利用路径 p 和从 i 到 j 中间节点均取自集合{1,2,…,k-1}的最短路径之间的关系。该关系依赖于节点 k 是否是路径 p 上的一个中间节点。
所有节点对最短路径问题的一个递归解
因此,我们可以定义一个求最短路径估计的递归公式。设 d(k)ij 为从节点 i 到节点 j 的所有中间节点全部取自集合 1,2,...,k 的一条最短路径的权重值。当 k=0 时,从节点 i 到节点 j 的一条不包括编号大于0的中间节点的路径将没有任何中间节点。这样的路径最多只有一条边,因此, d(0)ij=ωij 。因此,我们可以递归的定义 d(k)ij 如下:
d(k)ij={ωij,min{d(k−1)ij,d(k−1)ik+d(k−1)kj},若 k=0若 k≥1
其伪代码表示为:
void floyd() {
//初始化操作
D[N][N] = 图的权重W; // D[i][j] = W_{ij}
PATH[N][N] = -1;
for k = 1 to N
for i = 1 to N
for j = 1 to N
if (D[i][j] < D[i][k] + D[k][j] ) {
D[i][j] = D[i][k] + D[k][j];
path[i][j] = k;
}
}
其Floyd算法的时间复杂度为 O(N3)
Floyd算法的栗子请看 07-图4 哈利·波特的考试
Reference:
(1). 《算法导论》第三版 Thomas H.Cormen et al.
(2). 数据结构–陈越、何钦铭