一、Bellman-Ford算法
最优性原理
它是最优性原理的直接应用,算法基于以下事实:
l 如果最短路存在,则每个顶点最多经过一次,因此不超过n-1条边;
l 长度为k的路由长度为k-1的路加一条边得到;
l 由最优性原理,只需依次考虑长度为1,2,…,k-1的最短路。
适用条件&范围
l 单源最短路径(从源点s到其它所有顶点v);
l 有向图&无向图(无向图可以看作(u,v),(v,u)同属于边集E的有向图);
l 边权可正可负(如有负权回路输出错误提示);
l 差分约束系统(需要首先构造约束图,构造不等式时>=表示求最小值, 作为最长路,<=表示求最大值, 作为最短路。<=构图时, 有负环说明无解;求不出最短路(为Inf)为任意解。>=构图时类似)。
算法描述
l 对每条边进行|V|-1次Relax操作;
l 如果存在(u,v)∈E使得dis[u]+w<dis[v],则存在负权回路;否则dis[v]即为s到v的最短距离,pre[v]为前驱。
时空复杂度
for i:=1 to |V|-1 do
for 每条边(u,v)∈E do Relax(u,v,w);
for每条边(u,v)∈E do
if dis[u]+w<dis[v] Then Exit(False)
算法时间复杂度O(VE)。因为算法简单,适用范围又广,虽然复杂度稍高,仍不失为一个很实用的算法。
改进和优化 如果循环n-1次以前已经发现不存在紧边则可以立即终止; Yen氏改进(不降低渐进复杂度);SPFA算法
题目:poj3259 http://poj.org/problem?id=3259 代码如下:
#include <iostream> using namespace std; const int INF=999999999; struct node { int s; int t; int w; }wedge[5600]; int dis[510]; int F,N, M, W; int cnt=0; int bellmam_ford() { for(int i=0;i<=N;i++) dis[i] =INF; dis[1] = 0; bool ischange =false; for(int i=1;i<N;i++)//顶点数减一 { for(int j=1;j<=cnt;j++) //边数 if(dis[wedge[j].t] > dis[wedge[j].s]+wedge[j].w) { dis[wedge[j].t] = dis[wedge[j].s]+wedge[j].w; ischange = true; } if(!ischange) break; } for(int j=1;j<=cnt;j++) if(dis[wedge[j].t] > dis[wedge[j].s]+wedge[j].w) { return 1; //有环 } return 0; } int main() { cin>>F; while(F--) { cin>>N>>M>>W; cnt=0; int u,v,w; for(int i=0;i<M;i++) { cin>>u>>v>>w; cnt++; wedge[cnt].s=u; wedge[cnt].t=v; wedge[cnt].w=w; cnt++; wedge[cnt].s=v; wedge[cnt].t=u; wedge[cnt].w=w; } for(int i=0;i<W;i++) { cin>>u>>v>>w; cnt++; wedge[cnt].s=u; wedge[cnt].t=v; wedge[cnt].w = -w; } if(bellmam_ford()) cout<<"YES"<<endl; else cout<<"NO"<<endl; } system("pause"); return 0; }
二、 SPFA算法
算法简介
SPFA(Shortest Path Faster Algorithm)是Bellman-Ford算法的一种队列实现,减少了不必要的冗余计算。 它可以在O(kE)的时间复杂度内求出源点到其他所有点的最短路径,可以处理负边。
算法流程
SPFA对Bellman-Ford算法优化的关键之处在于意识到:只有那些在前一遍松弛中改变了距离估计值的点,才可能引起他们的邻接点的距离估计值的改变。因此,算法大致流程是用一个队列来进行维护,即用一个先进先出的队列来存放被成功松弛的顶点。初始时,源点s入队。当队列不为空时,取出队首顶点,对它的邻接点进行松弛。如果某个邻接点松弛成功,且该邻接点不在队列中,则将其入队。经过有限次的松弛操作后,队列将为空,算法结束。SPFA算法的实现,需要用到一个先进先出的队列 queue 和一个指示顶点是否在队列中的标记数组mark。为了方便查找某个顶点的邻接点,图采用临界表存储。
算法代码
Procedure SPFA;
Begin
initialize-single-source(G,s);
initialize-queue(Q);
enqueue(Q,s);
while not empty(Q) do begin
u:=dequeue(Q);
for each v∈adj[u] do begin
tmp:=d[v]; relax(u,v);
if (tmp<>d[v]) and (not v in Q) then enqueue(v);
end;
end;
End;
负环处理
需要特别注意的是:仅当图不存在负权回路时,SPFA能正常工作。如果图存在负权回路,由于负权回路上的顶点无法收敛,总有顶点在入队和出队往返,队列无法为空,这种情况下SPFA无法正常结束。
判断负权回路的方案很多,世间流传最广、比较容易实现并且高效的方法的是记录每个结点进队次数,超过|V|次表示有负权。
题目:POJ 1062 http://poj.org/problem?id=1062 代码如下:
#include <iostream> #include <queue> using namespace std; int dis[101]; int rank[101]; bool vi[101]; //看是否在队列中 int wedge[101][101]; int ori; int M,N; int SPFA() //short path fast algorithm { for(int i=0;i<=N;i++) { dis[i] = 99999999; vi[i] = false; //不在队列中 } dis[1] =0; queue<int> q; q.push(1); vi[1] = true; while(!q.empty()) { int t = q.front();q.pop(); vi[t] = false; //不在队列中 for(int j=0;j<=N;j++) { if(wedge[t][j]!=-1 && rank[j]<=ori+M && rank[j]>=ori && wedge[t][j]+dis[t]<dis[j]) { dis[j] = wedge[t][j]+dis[t]; if(!vi[j]) //不在队列,则加入队列里 { q.push(j); vi[j] = true;//加入队列 } } } } return dis[0]; } int main() { cin>>M>>N; int temp; for(int i=0;i<=N;i++) for(int j=0;j<=N;j++) wedge[i][j] = -1; for(int i=1;i<=N;i++) { cin>>wedge[i][0]>>rank[i]>>temp; int u,w; while(temp--) { cin>>u>>w; wedge[i][u] = w; } } rank[0] = rank[1]; int min = 99999999; int rev=0; for( ori =rank[1]-M ;ori<=rank[1];ori++ ) { rev = SPFA(); if(rev < min) min = rev; } cout<<min<<endl; system("pause"); return 0; }
三、Floyd算法
题目:POJ 2253 http://poj.org/problem?id=2253 代码如下:
#include <iostream> #include <math.h> using namespace std; struct point { int x; int y; }; const int INF = 9999999; point arr[202]; //double dis[202][202]; double wedge[202][202]; int cnt=0; int N=0; double ford() { // memcpy(dis,wedge,sizeof(dis)); for(int k=0;k<N;k++) for(int i=0;i<N;i++) for(int j=0;j<N;j++) { if(wedge[i][k]<wedge[i][j]&&wedge[k][j]<wedge[i][j]) wedge[i][j] = max(wedge[i][k],wedge[k][j]); } return wedge[0][1]; } int main() { while(cin>>N,N) { cnt++; for(int i=0;i<N;i++) cin>>arr[i].x>>arr[i].y; for(int i=0;i<N;i++) { wedge[i][i] = 0; for(int j=i+1;j<N;j++) wedge[i][j] = wedge[j][i] =sqrt((arr[i].x -arr[j].x+0.0)*(arr[i].x -arr[j].x+0.0)+(arr[i].y -arr[j].y+0.0)*(arr[i].y -arr[j].y+0.0)); } double rev=ford(); printf("Scenario #%d/nFrog Distance = %.3f/n/n",cnt,rev); } return 0; }
四、Dijkstra算法