floyd算法是一个很强大的算法,它可以计算任意两点之间的最短路径,其边可以为负值。
时间复杂度n^3
1 void floyd() 2 { 3 int k,u,v; 4 for(k=0;k<G.vunm;k++) 5 for(u=0;u<G.vunm;u++) 6 for(v=0;v<G.vunm;v++) 7 { 8 if(D[u][v]>D[u][k]+D[k][v]) 9 { 10 D[u][v]=D[u][k]+D[k][v]; 11 } 12 } 13 printf("%d ",D[s][t]==MAX?-1:D[s][t]); 14 }
补充一下:对于floyd判断负环是否存在只需检查是否存在d[i][i]是负数的顶点i 即可
传送门: http://poj.org/problem?id=3259
SPFA算法 :设立一个先进先出的队列用来保存待优化的结点,优化时每次取出队首结点u,并且用u点当前的最短路径估计值对离开u点所指向的结点v进行松弛操作,如果v点的最短路径估计值有所调整,且v点不在当前的队列中,就将v点放入队尾。这样不断从队列中取出结点来进行松弛操作,直至队列空为止。
SPFA 是这样判断负环的: 如果某个点进入队列的次数超过N次则存在负环(SPFA无法处理带负环的图)
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<queue> 5 #define MAX 999999999 6 7 using namespace std; 8 9 int G[503][503]; 10 int n; //n为点数 11 12 bool SPFA() 13 { 14 int u,i; 15 queue<int>que; 16 int dis[503];//存最短距离 17 bool vis[503];//是否访问 18 int flag[503];//判断是否为负环 19 20 memset(flag,0,sizeof(flag)); 21 memset(vis,false,sizeof(vis)) ; 22 fill(dis,dis+n+1,MAX); 23 dis[1]=0; 24 que.push(1); 25 while(!que.empty()) 26 { 27 u=que.front(); 28 que.pop(); vis[u]=false; 29 for(i=1;i<=n;i++) //对每个顶点更新相当于BF中通过每个边更新一样 30 { 31 if(dis[i]>dis[u]+G[u][i]) 32 { 33 dis[i]=dis[u]+G[u][i]; 34 if(!vis[i])//可能多次进入。如果之前进入,则不用再进,因为不用进入也可以i的最短路径值,如果在进入,就累赘了。 35 { 36 vis[i]=true; 37 flag[i]++; 38 if(flag[i]>=n) //表示存在负环; 39 return true; 40 que.push(i); 41 } 42 } 43 } 44 } 45 return false; 46 } 47 48 int main() 49 { 50 int t,k,i,j,u,v,w,m; 51 52 scanf("%d",&t); 53 while(t--) 54 { 55 scanf("%d%d%d",&n,&m,&k); 56 for(i=1;i<=n;i++) 57 for(j=1;j<=n;j++) 58 G[i][j]=i==j?0:MAX; //i=j表示值0 否则为max 59 for(i=0;i<m;i++) 60 { 61 scanf("%d%d%d",&u,&v,&w); 62 G[u][v]=G[v][u]=w; 63 } 64 for(i=0;i<k;i++) 65 { 66 scanf("%d%d%d",&u,&v,&w); 67 G[u][v]=-w; //一定加符号,负权 68 } 69 printf("%s ",SPFA()?"YES":"NO"); 70 } 71 return 0; 72 } 73 74
SLF:Small Label First 策略,设要加入的节点是j,队首元素为i,若dist(j)<dist(i),则将j插入队首,否则插入队尾。
SPFA算法对于稀疏图才能发挥它的大作用,对于稀疏图我们用到的数据结构为 前向星
下面就是 SPFA+前向星的程序 并应用了SLF 双向队列进行优化
1 #include<iostream> 2 #include<cstring> 3 #include<cstdio> 4 #include<queue> 5 #define MAX 9999999 6 7 using namespace std; 8 9 struct node { 10 int v,w;//v终点,w权值 11 int nest;//下一个 12 }; 13 node edge[5203];//前向星 每个元素即每条边 14 int head[503];//头指针式的数组 即邻接链表的指针数组 15 int cnt;//下标 16 int n;//点的个数 17 18 void add(int u,int v,int w)//加边 19 { 20 edge[cnt].v=v; 21 edge[cnt].w=w; 22 edge[cnt].nest=head[u];//相当于链表头插入法 23 head[u]=cnt++; // 24 } 25 26 bool SPFA() 27 { 28 int i,u,v;//u从Q中取出的点 v找到的点 29 int dis[503];//保存每点最短距离 30 int flag[503];//保存某点加入队列的次数 31 bool vis[503];//标记数组 32 deque<int> que;//双向队列 33 34 fill(dis,dis+n+1,MAX); 35 memset(flag,0,sizeof(flag)); 36 memset(vis,false,sizeof(vis)); 37 dis[1]=0;//s为1 即1为起点 38 que.push_back(1);//将1加入队列 39 while(!que.empty()) //队列不为空 40 { 41 u=que.front();//队列中取出 42 que.pop_front();//删除 43 vis[u]=false;//标记为未访问 44 for(i=head[u];i!=-1;i=edge[i].nest) //对所有与该点相邻的边进行查找 45 { 46 v=edge[i].v; 47 if(dis[v]>dis[u]+edge[i].w) 48 { 49 dis[v]=dis[u]+edge[i].w;//松弛成功 50 if(!vis[v])// 表示未标记 51 { 52 vis[v]=true;//标记 53 flag[v]++;//表示该点进入队列的次数 54 if(flag[v]>=n)//若该点进入队列次数超过n次 说明有负环 55 return true;//返回有负环 56 //以下为SLF优化 57 if(!que.empty()&&dis[v]<dis[que.front()]) //若为队列为空&&队列队首元素距离大于当前点的距离 58 que.push_front(v);//加入到队首 59 else 60 que.push_back(v); 61 } 62 } 63 } 64 } 65 return false;//没有负环 66 } 67 68 int main() 69 { 70 int u,v,w,m,k,t; 71 72 scanf("%d",&t); 73 while(t--) 74 { 75 memset(head,-1,sizeof(head)); 76 cnt=0; 77 scanf("%d%d%d",&n,&m,&k); 78 while(m--) 79 { 80 scanf("%d%d%d",&u,&v,&w); 81 add(u,v,w); add(v,u,w); //双向无向图 82 } 83 while(k--) 84 { 85 scanf("%d%d%d",&u,&v,&w); 86 add(u,v,-w); 87 } 88 printf("%s ",SPFA()?"YES":"NO"); 89 } 90 return 0; 91 } 92