Dijkstra:适用于权值为非负的图的单源最短路径,用斐波那契堆的复杂度O(E+VlgV)
BellmanFord:适用于权值有负值的图的单源最短路径,并且能够检测负圈,复杂度O(VE)
SPFA:适用于权值有负值,且没有负圈的图的单源最短路径,论文中的复杂度O(kE),k为每个节点进入Queue的次数,且k一般<=2,但此处的复杂度证明是有问题的,其实SPFA的最坏情况应该是O(VE).
Floyd:每对节点之间的最短路径。
先给出结论:
(1)当权值为非负时,用Dijkstra。
(2)当权值有负值,且没有负圈,则用SPFA,SPFA能检测负圈,但是不能输出负圈。
(3)当权值有负值,而且可能存在负圈,则用BellmanFord,能够检测并输出负圈。
(4)SPFA检测负环:当存在一个点入队大于等于V次,则有负环,后面有证明。
本文针对SPFA算法进行分析。
本文解决问题有:
(1)证明SPFA算法最坏复杂度。
(2)为什么存在一个点进入队列V次,就说图有负环。
SPFA是西安交通大学的段凡丁在1994年与《西安交通大学学报》中发表的“关于最短路径的SPFA快速算法”,他在里面说SPFA速度比Dijkstra快,且运行V次的SPFA速度比Floyd速度快,当时我就产生了疑惑:为什么他这么快,在一些经典的书籍中都没有出现过,也没被提及过。
事实证明SPFA算法是有局限的,他不适用于稠密图,对于特别情况的稠密图,SPFA复杂度和BellmanFord时间一样。
最优时间复杂度先不看。
下面来证明SPFA最坏时间复杂度:
思路:
(1)找出SPFA的最最坏到不可能的情况的复杂度为O(VE)。
(2)找出SPFA确实有图,使得跑SPFA的复杂度为O(VE)。
我原本想举一个例子来说明SPFA存在O(VE)的情况,但是确实,最坏情况复杂度是不能用举例说明的,谢谢TianMingBu老师的指出。
证明如果有负环当且仅当存在一个点入队列次数大于等于V次。
对于某个点v,我们已知s到v的松弛路径的边的数量最多为V-1。
我这里说的松弛路径指的是:比如s直接松弛v,这样就有一条松弛路径:s->v 。s松弛a,a松弛v,则s->a->v就是一条松弛路径。
对于所有s到v的松弛路径来说,当松弛路径边的数量相等时,v只入队一次。
比如有松弛路径:
s->a->x->v
s->b->x->v
s->c->z->v,可以看出v只入队一次。
因为s到v的松弛路径的长度最多可以有V-1种变化,所以v最多入队V-1次。
举个例子:
假设有一个图,点集为{s,a,b,c,v},则最多可能的松弛路径有:
s->v
s->a->v
s->b->v
s->c->v
s->a->b->v
s->a->c->v
s->b->c->v
s->a->b->c->v
则松弛路径的边数变化有1,2,3,4,所以v入队为4次,即V-1次。
所以我们可以说每个点最多入队V-1次,因此我们求最坏情况为每个点都入队V-1次,所以此时:
这里举个最坏情况的例子。
当然我们可能考虑,当给定一个V的值,E的值,比如E=2V,怎么给出一个图,使得对此图运行SPFA算法的复杂度为O(VE).
我们这里假定图是连通的,所以E>=V-1。
方法如下:
(1)我们首先将图组成一个链,即如下图所示:
这样就用去了V-1条边。
(2)分别添加v0连向v2,v3,....vk的边,我们要添加的这些边的权值要满足v0先更新vk,v0更新vk-1后vk-1还能更新vk,以此类推,如下图所示:
(3)以vk,vk-1,.....v1的顺序添加权值为正无穷的自环,且不断循环,这个步骤是为了保持v1到vk点的出度保持一致,所以这样做,如下图:
这样我们就构造了一个能够让SPFA跑出O(VE)的图了,原因如下:
因为我们E的值和V的值是不确定的,所以很有可能不能够完成上述的这些构造,我们会分析当没有剩余的边构造上面的步骤(2)时的复杂度(也就是说E<=2V-3,因为第一步连成一个链需要V-1条边,而第二步v0连出去的边需要V-2),和有足够的边能构造上面的图这两种情况。
(1)如果E<=2V-3
因为E<=2V-3,所以E=O(V),所以只要能够求出复杂度是O(V^2),即可说为O(VE).
我们要计算所有点的入队次数和访问的边数。
V0出度为 E-V+2,V0入队1次。
V1出度为1,V1入队为1次。
V2出度为1,V2入队为2次(分别为v0松弛v2,v1松弛v2)。
V3出度为1,V3入队为3次。
....
V(e-v+2)出度为1,入队次数为(E-V+2)次。
后面V(e-v+3),V(e-v+4),.....V(k-1)的出度为1,入队次数为E-V+2次。 这些点的个数为V-(E-V+3)-1 = 2V-E-4。
vk出度为0,vk入队为E-V+2次。
所以总共的访问的边数为:
(2)如果E>2V-3
此时构造图的第二步已经完毕,所以后面剩余的边只需要不断添加自环保持出度平衡即可。
V0出度为V-1,入队1次。
V1到Vk出度为(E-V+2)/(V-1) 或(E-V+2)/(V-1)+1。
v1到vk的入队次数分别为1,2,3,.....V-1。
所以总共访问边数为:
package C24;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import C22.GraphFactory;
import C22.Pair;
import C22.Weighted_Adjacent_List;
public class SPFA {
public int[] spfa(Weighted_Adjacent_List G,String s){
return spfa(G,G.getVertexIndex(s));
}
public int[] spfa(Weighted_Adjacent_List G,int s){
//1.创建所要的数据结构
int size = G.getSize();
int d[] = new int[size]; //距离估计
for(int i=0;i<d.length;i++){
d[i] = Integer.MAX_VALUE;
}
List<Integer> Q = new LinkedList<Integer>();
boolean is_in_queue[] = new boolean[size]; //是否在队列中
for(int i=0;i<is_in_queue.length;i++){
is_in_queue[i] = false;
}
//2.初始化
d[s] = 0;
Q.add(s);
is_in_queue[s] = true;
//3.核心
while(!Q.isEmpty()){
int u = Q.remove(0);
is_in_queue[u] = false;
List<Pair> list = G.getListByVertexIndex(u);
Iterator<Pair> iter = list.iterator();
while(iter.hasNext()){
Pair vstr = iter.next();
int v = G.getVertexIndex(vstr.end);
if(d[v]>d[u]+vstr.weight){
d[v] = d[u] + vstr.weight;
if(!is_in_queue[v]){ //如果松弛的点不在队列中,则加入队列;如果在队列中,则不动
Q.add(v);
is_in_queue[v] = true;
}
}
}
}
return d;
}
public static void main(String[] args) throws Exception {
SPFA spfa_alg = new SPFA();
Weighted_Adjacent_List g = GraphFactory.getWeightedAdjacentListInstance("input\\weighted_graph.txt");
int[] d = spfa_alg.spfa(g,"s");
for(int i=0;i<d.length;i++){
System.out.println(g.getVertexValue(i)+":"+d[i]);
}
}
}