spfa和bellman-ford复杂度是同阶的。虽然据说spfa复杂度是O(K*E)(k为某个常数,2~3),但是最坏情况下为O(N*E),卡spfa的题目无法通过。
所以有正边的时候最好用dikstra+heap优化,并且有priority queue代码量不会很大
bellman-ford算法的核心是松弛操作:
用一个while(true)循环;不断检测对于每条边,可以不可以进行松弛操作,如果没有一条边可以进行松弛操作,则退出循环。在没有负边的时候,松弛操作一定是有限的,故最后一定会退出循环。
如果图中不存在负环,那么最短路就不会经过同一个顶点两次
代码
sturct edge{ int from,to,cost; };
edge es[maxn];
int dis[maxn];
int V,E;
void BellmanFord(int s){
for(int i=0;idis[from]+e.cost){
dis[e.to]=dis[e.from]+e.cost;
update=true;
}
}
if(!update) break;
}
}
考虑:为什么要循环V-1次?
答:因为最短路径肯定是个简单路径,不可能包含回路的,
如果包含回路,且回路的权值和为正的,那么去掉这个回路,可以得到更短的路径
如果回路的权值是负的,那么肯定没有解了
图有n个点,又不能有回路
所以最短路径最多n-1边
又因为每次循环,至少relax一边
所以最多n-1次就行了
所以可以把while(true)改为 for(int i=0;i
在bellman-ford算法中,如果d[i]还不是最短距离的话,那么即使进行d[j]=d[i]+e.cost的更新,dis[j]也不会编程最短距离,而且,即使dis[i]没有变化,每次循环也要检查一下所有边,这是浪费时间的。
因此,进行以下优化:
(1)找到最短距离已经确定的顶点,从它出发更新顶点的最短距离,标记这个点
(2)以后不再关心标记的店
这实质上是一种DP,满足最优子结构
Dijkstra代码
int cost[maxn][maxn];
int dis[maxn];
int used[maxn];
int V;
void Dikstra(int s){
fill(dis,dis+V,INF);
fill(used,used+V,false);
dis[s]=0;
while(true){
int nextv=-1;
for(int i=0;idis[nextv]+cost[nextv][i])
dis[i]=dis[nextv]+cost[nextv][i];
}//松弛
}
}
每次寻找最小点 V,松弛 E
由于每次Dikstrea都考虑一个最小的点,我们可以优化插入(松弛更新)和取出最小值两个操作,因此使用堆。但是回想dijkstra算法中,for(1..v)的大循环内,每次在unknown的集合中找到dist[]数组里最小的那个,从unknown集合中删除该结点。朴素实现需要O(V)的时间,而用堆可以用O(log(V))的时间找到。然而,光找到还没完,找到之后要松弛所有与他相邻的unknown的结点(known的结点已经都是最短路了)。注意到如果要用堆实现优化,堆中存储的结点的priority值应当是图中该店当前的离source的距离。然而松弛操作会更新该距离,这就意味着我们要到堆内部找到这个结点的位置,更新距离,再维护堆。而堆是不提供检索功能的,找到一个结点需要O(V),完全糟蹋了O(log V)的优化了。更何况STL的priority_queue没有办法去接触内部的数据了。
其实,有一个可行的解决方案:一旦某个结点被更新了距离,一律但当成新结点加进堆里。这样会造成一个一个问题:一个图中的结点可能在堆中可能同时会存在好几个。但是这个不影响结果:先出堆的一定是距离最小的那个结点。其他的结点出堆的时候图里面的对应结点一定已经在known的集合了,到时候直接无视掉弹出来的已经known的结点,继续弹下一个,知道弹出一个unknown的结点为止。这个做法最坏可能会让堆的高度加倍,然而仍然不影响O(log n)的复杂度(注意一定要用一个标记标注已经使用的节点,不然因为堆中包含一个顶点的多个版本,当弹出已经使用的顶点时,虽然不会改变结果(因为这个顶点的更小版本已经优化过了),但是徒增复杂度)
(来自http://blog.csdn.net/biran007/article/details/4088132)
int cost[maxn][maxn];
int dis[maxn];
int used[maxn];
int V;
int NumOfHeap;
typedef pair P;
P heap[3*maxn];
void Dikstra(int s){
fill(dis,dis+V,INF);
fill(used,used+V,false);
dis[s]=0;
NumOfHeap=0;
heap[++NumOfHeap]=make_pair(s,0);
while(NumOfHeap){
P cur=heap[1];
heap[1]=heap[--NumOfHeap];
sink(1,NumOfHeap);
if(used[cur.first]) continue;//如果被标记,弹出
int curdis=cur.second;
int curname=cur.first;
used[curname]=true;
for(int i=0;icost[curname][i]+curdis){
dis[i]=curdis+cost[curname][i];
heap[++NumOfHeap]=make_pair(i,dis[i]);
swim(NumOfHeap);
}
}
}
}