单源最短路径,在现实中是很多应用的,是图的经典应用,比如在地图中找出两个点之间的最短距离、最小运费等。单源最短路径的问题:已知图G=(V,E),找出给定源顶点s∈V到每个顶点v∈V的最短路径。单源最短路径衍生出的变体问题如下:
1)单终点最短路径问题:找出从每个顶点v到指定终点t的最短路径。这个是单源最短路径的反向,把图的每条边反向,问题就变成单源最短路径的问题;
2)单对顶点最短路径问题:对于给定顶点u和v,找出从u到v的一条最短路径。找出所有顶点的单源最短路径,该问题自然得解。
3)每队顶点间最短路径问题:对于给定顶点u和v,找出从u到v的最短路径。单独讨论
针对单源最短路径问题,构建如下的数学模型来描述:
给定的带权有向图G=(V,E),加权函数w:E->R为从边到实型权值的映射。路径p=
如果u到v存在一条路径,则其最小权值的边集合为最短路径。
最短路径算法具有动态规划和贪心算法的最优子结构性质:最短路径的子路径是最短路径,即一条顶点间的最短路径包含路径上其他的最短路径。
对图求解最短路径的问题,存在负权值边和负权回路的情况。经过负权值边,权值会减小,而无限次经过负权回路,权值会趋近-∞,所以一条最短路径是不应该包含负权回路。在最短路径算法总,Dijkstra算法假定图的所有边权值都非负;而Bellman-Ford算法,允许图存在负权边,但不能存在从源点可达的负权回路,该算法还能检测出负权回路。
最短路径之于一个图的发现,其实是一棵有根的最短路径树,只要回溯每个顶点的父顶点即可。定义定点集Vπ为G总所有具有非空前趋(存在负顶点)的顶点集合,再加上源点s。
Vπ={v∈V:π[v]≠null}U{s}
有向边集Eπ是有Vπ中的顶点π值导出的边集:
Eπ={(π[v],v)∈E:v∈Vπ-{s}}
Gπ(Vπ,,Eπ)就是最短路径树,s是根节点。
设图G=(V,E)是带权有向图,其加权函数w:E->R,并假定G中不包含从源点s∈V可达的权值为负的回路,那么最短路径是良定义 。以s为根的最短路径树是有向子图Gπ(Vπ,,Eπ),其中Vπ⊆V,Eπ⊆E,那么:
1)Vπ是G中从s可达的顶点集合;
2)Gπ形成一颗以s为根的有根树;
3)对所有v属于Vπ,Gπ中从s到v的唯一简单路径是G中从s到v的最短路径。
当然最短路径并不是唯一的,最短路径树也是。
导论中给出松弛技术,就是表示紧缩上界的方法。对每个顶点v∈V,设置一个属性d[v],表示从源点s到v的最短路径上权值的上界,称为最短路径估计。松弛操作,对每个顶点d[v]初始为∞,松弛一条边(u,v)可以减小最短路径估计的值d[v],并更新v的前趋π[v]。
Relax_Func(){
if d[v]>d[u]+w(u,v)
then d[v]=d[u]+w(u,v)
π[v]=u
}
松弛操作,通俗地说,就是先设置d[v]最大,然后找出其边权来更新d[v]和π[v],从而不断减小d[v]值,是算法的一种指示变量。
最短路径和松弛操作是最短路径算法的基础,具有三角不等式、上界、无路径、收敛、路径松弛、前趋子图六个性质。还有一个无穷运算的约定:
假定对任何实数a≠-∞,有a+∞=∞+a=∞,出现负权回路下,假定对任何实数a≠∞,有a+(-∞)= (-∞)+a=-∞。
1)Bellman-Ford算法
Bellman-Ford算法支持存在负权边的情况下,解决单源最短路径问题。输入给定的带权有向图G=(V,E),其源点为s,加权函数为w:E->R,执行Bellman-Ford算法后输出一个返回值,表明图中是否存在着一个从源点可达的权为负的回路。如存在,则无解,否则将产生最短路径及其权值。
简单来说,Bellman-Ford算法的执行结果将确认是否存在负权回路,如果没有就会产生最短路径及其权值。具体算法如下:
Func_Bellman-Ford(G,w,s){
Func_Initialize-single-source(G,s);//初始化每个顶点的d和π值
//开始进行松弛操作
for i=1 to |V(G)|-1
do for each edge (u,v)∈E(G)
do Func_Relax(u,v,w)
for each edge(u,v) ∈E(G)
do if d[v]>d[u]+w(u,v) //已经松弛操作了,如果还存在子顶点大于负顶点权值的情况,则存在负权回路
then return false //存在负权回路,返回false
return true
}
Func_Relax(u,v,w){
if d[v]>d[u]+w(u,v)
then d[v]= d[u]+w(u,v)
π[v]=u
}
Func_Initialize-single-source(G,s){
for eachvertex v∈V[G]
do d[v]=∞
π[v]=null
d[s]=0
}
Bellman-Ford算法的运行时间为O(VE),主要是如何证明该算法执行结果的正确性。算法导论中通过反证法和三角不等式证明了该算法可以正确计算出从源点出到所有顶点的最小路径的权。
有兴趣可以理解下证明过程,不过中心思想依然是说明不存在负权回路情况下从源点到所有顶点间的最小路径权值。
在有向无回路情况下,按顶点的拓扑序列对某加权dag图(有向无回路图)G=(V,E)的边进行松弛后,可以在⊙(V+E)时间内计算出单源最短路径。Dag图最短路径总是存在的,即使图中有权值为负的边存在,也不可能存在负权回路。
有向无回路图中的单源最短路径算法开始对dag图进行拓扑排序(运用DFS),获得顶点的线性序列。如果从顶点u到v存在一条路径,则在拓扑序列中u先于v;对顶点线性序列的每个顶点做松弛操作即获得单源最短路径。算法的典型应用是在PERT图分析中确定关键路径。关键路径是通过dag的一条最长路径,对应于执行一个有序的工作序列的最长时间。关键路径的权值是完成所有工作所需要时间的下限。理解下就是这个算法适合于序列图。
2)Dijkstra算法
Dijkstra算法解决有向图G=(V,E)上带权的单源最短路径问题,但要求所有边的权值非负,即每条边(u,v)∈E,有w(u,v)≥0。Dijkstra算法有一个顶点集合S,包含具有最短路径的顶点。算法反复选择具有最短路径估计的顶点u∈V-S,将u加入S,对u的所有边进行松弛操作。算法采用了最小优先队列Q来排序关键字为顶点的d值。最小优先队列操作有插入、抽取最小、删除三个操作,不同数据结构实现由不同的时间性能:第一种数组结构,从1到|V|编好号的顶点,简单将d[v]存入一个数组的第v项,插入和删除操作都是O(1),但搜索最小时间是O(V);第二种二叉最小堆结构,时间性能有提升;第三种是斐波那契堆,时间性能也会有提升。Dijkstra算法和最小生成树的Prim算法相似。下面看具体算法:
Func_Relax(u,v,w){
if d[v]>d[u]+w(u,v)
then d[v]= d[u]+w(u,v)
π[v]=u
}
Func_Initialize-single-source(G,s){
for eachvertex v∈V[G]
do d[v]=∞
π[v]=null
d[s]=0
}
Func_Dijkstra(G,w,s){
Func_Initialize-single-source(G,s)
S=∅
Q=V[G] //包含所有顶点,最小优先队列插入
While Q≠∅
do u=Extract-Min(Q) //最小优先队列搜索最小值
S=SU{u}
for each vertex v∈Adj[u]//所有u的邻接表中的顶点
do Func_Relax(u,v,w) //包含最小优先队列u出列
}
Dijkstra算法总是在V-S中选择最轻或最近的顶点插入集合S中,是一种贪心策略。算法导论中证明了Dijkstra算法应用贪心策略可以得出最短路径。证明过程采用的数学思路也很值得去细细品味,包括最短路径的性质的证明:三角不等式、上界性质、无路径性质、收敛性质、路径松弛性质和前驱子图性质(构建以s为根的最短路径树)。在算法中,树和图是有着紧密联系。
3)差分约束系统与最短路径
将计算机中的某类问题求解转化成数学模型来求解,是一以贯之的。同样的,将不包含负权回路的图G=(V,E)求解单源最短路径问题转为线性规划中的差分约束系统模型来求解,而Bellman-Ford算法实际就是一种线性规划算法。
一般的线性规划问题(linear-programmingproblem)中,给定一个mXn的矩阵A,一个m维向量b和一个n维向量c,找出由n个元素组成的向量x,在由Ax≤b所给出的m个约束条件下,使目标函