写在前面
我们现在熟悉的Dijkstra算法基本思路是:每次寻找已知节点到达未知节点距离最短的一个节点,那么这个位置节点的最短距离我们就知道了,这个未知节点变为已知节点,再去更新未知节点的距离......那么这样我们就需要寻找N-1次就可以找到最终的答案,因为每次我们都会得到一个点的最短距离,当然,Dijkstra算法因为每次都要去找距离已知点中距离最短的点,那么我们就可以使用优先队列来进行优化,这样我们就不用再去遍历寻找了
Bellman-Ford算法相对于Dijkstra算法,他不仅可以得到单源最短路的长度,并且我们还可以判断有没有负环产生,那么我们这里的SPFA算法就是对Bellman-Ford算法的队列优化
算法解释
在SPFA算法中,我们已知一个起点,以及点之间的距离,我们还需要定一个队列,在队列中存放后面可以用来进行松弛操作的点,数组mark表示某个点进入队列的次数
从开始节点遍历边,当遍历到的点不在队列中的时候,将点放入队列,mark对应的值++,并且更新最短距离,当某一个节点进入队列次数大于n的时候,我们就可以判定图中存在负环
模板
#include <iostream> #include <cstdio> #include <cstring> #include <cmath> #include <queue> #include <algorithm> #define INF 0x3f3f3f3f using namespace std; const int MAXN = 5500; int n,m,w; struct Edge { int v,w,next; }edge[MAXN]; int head[MAXN],dis[MAXN],vis[MAXN],t; void Init() { memset(head,-1,sizeof(head)); t = 0; } void Add_edge(int u,int v,int w) { edge[t].v = v;edge[t].w = w;edge[t].next = head[u];head[u] = t++; } bool SPFA() { int mark[MAXN];//记录每个点如队列的次数 for(int i = 1;i <= n;i ++) { mark[i] = 0;dis[i] = INF;vis[i] = 0; } queue<int> q; q.push(1); //我们只需要判断负环,随便找一个起点就好 dis[1] = 0; vis[1] = 1;//入队列 mark[1] ++; while(!q.empty()) { int u = q.front(); q.pop(); vis[u] = 0;//出队列 for(int i = head[u];i != -1;i = edge[i].next) { int v = edge[i].v; if(dis[v] > dis[u] + edge[i].w) { dis[v] = dis[u] + edge[i].w; if(!vis[v])//不在队列中的时候出队 { q.push(v);mark[v] ++;vis[v] = 1; } if(mark[v] >= n)//如果不存在负环,那么最多更新n-1次就可以得到最终的答案,因为一次最少更新一个节点,那么如果出现了更新n次,那么就一定出现了负环 return false; } } } return true; } int main() { int T; scanf("%d",&T); while(T--) { Init(); int u,v,z; scanf("%d%d%d",&n,&m,&w); for(int i = 0;i < m;i ++) { scanf("%d%d%d",&u,&v,&z); Add_edge(u,v,z); Add_edge(v,u,z); } for(int i = 0;i < w;i ++) { scanf("%d%d%d",&u,&v,&z); Add_edge(u,v,-z); } if(!SPFA()) printf("YES\n"); else printf("NO\n"); } return 0; }