今天学了一下BellmanFord算法(它也是一个求单元最短路的算法),它和不使用优先队列的Dijkstra算法复杂度差不多,但是它可以处理带有负边权的图。
算法实现很简单:给定图G=(V, E), |V| = N。 (1)我们对所有边进行N-1次松弛操作可以得到源点到所有点的最短距离。(2)再对所有边进行一次松弛操作,判断是否存在负环,如果存在负环,则从S到所有点的最短距离不存在,否则求解完毕。
这道题题意:给定一些双向边(普通路径),边权为正,再给定一些单向边,边权为负,问是在这个图里否存在一个负环,存在负环就输出YES,否则输出NO。
直接用这个算法就A了。
#include<stdio.h>
#include<string.h>
const int N = 512;
const int M = 6000; //邻接表存双向边要开边数的双倍
const int INF = 0x3f3f3f3f;
struct Edge {
int v, w;
int next;
};
int dis[N];
int fir[N];
Edge edge[M];
int cnt, n;
void init() {
cnt = 1;
memset(fir, -1, sizeof(fir));
}
void addEdge(int u, int v, int w) {
edge[cnt].v = v;
edge[cnt].w = w;
edge[cnt].next = fir[u];
fir[u] = cnt++;
}
bool bellmanFord(int s) {
memset(dis, INF, sizeof(dis));
dis[s] = 0;
for(int i = 1; i < n; i++) {
for(int j = 1; j <= n; j++) {
for(int k = fir[j]; k != -1; k = edge[k].next) {
int v = edge[k].v;
if(dis[v] > dis[j] + edge[k].w)
dis[v] = dis[j] + edge[k].w;
}
}
}
for(int j = 1; j <= n; j++) {
// printf("[%d]\n", dis[j]);
for(int k = fir[j]; k != -1; k = edge[k].next) {
int v = edge[k].v;
if(dis[v] > dis[j] + edge[k].w)
return true;
}
}
return false;
}
int main() {
int f;
scanf("%d", &f);
while(f--) {
init();
int m, w;
scanf("%d%d%d", &n, &m, &w);
for(int i = 0; i < m; i++) {
int s, e, t;
scanf("%d%d%d", &s, &e, &t);
addEdge(s, e, t);
addEdge(e, s, t);
}
for(int i = 0; i < w; i++) {
int s, e, t;
scanf("%d%d%d", &s, &e, &t);
addEdge(s, e, -t);
}
if(bellmanFord(1))
printf("YES\n");
else printf("NO\n");
}
return 0;
}
Bellman-Ford能解决的通常SPFA都能解决(SPFA是Bellman-Ford的另一种实现方法,故SPFA也是求单元最短路),而且SPFA效率更高。
SPFA的主要步骤:
需要用的存储结构:
邻接表。
队列que<int>。
dis[ u ]:从源点S到u的最短距离。
vis[ u ]:u是否被访问过。
outque[ u ]: u出队的次数。
(1)源点S入队。
(2)访问队首元素,并让其出队,该点出队次数+1,判断该点出队次数是否大于N, 若果大于N则图中存在负环路,否则访问该点的所有连接边,进行松弛操作,如果可以松弛,则更新dis并且把当前顶点入队。
(3)重复(2)直到队列为空。
#include<stdio.h>
#include<string.h>
#include<queue>
using namespace std;
const int N = 512;
const int M = 6000;
const int INF = 0x3f3f3f3f;
struct Edge {
int v, w, next;
};
Edge edge[M];
int fir[N];
int outque[N];
int vis[N], dis[N];
int cnt, n;
void init() {
cnt = 1;
memset(fir, -1, sizeof(fir));
}
void addEdge(int u, int v, int w) {
edge[cnt].v = v;
edge[cnt].w = w;
edge[cnt].next = fir[u];
fir[u] = cnt++;
}
bool SPFA(int s) {
memset(dis, INF, sizeof(dis));
memset(vis, 0, sizeof(vis));
memset(outque, 0, sizeof(outque));
queue<int> que;
dis[s] = 0;
que.push(s);
while(!que.empty()) {
int now = que.front();
outque[now] ++;
if(outque[now] > n) return false;
vis[now] = 0;
que.pop();
for(int i = fir[now]; i != -1; i = edge[i].next) {
int v = edge[i].v;
if(dis[v] > dis[now] + edge[i].w) {
dis[v] = dis[now] + edge[i].w;
vis[v] = 1;
que.push(v);
}
}
}
return true;
}
int main() {
int f;
scanf("%d", &f);
while(f--) {
int w, m;
init();
scanf("%d%d%d", &n, &m, &w);
for(int i = 0; i < m; i++) {
int u, v, val;
scanf("%d%d%d", &u, &v, &val);
addEdge(u, v, val);
addEdge(v, u, val);
}
for(int i = 0; i < w; i++) {
int u, v, val;
scanf("%d%d%d", &u, &v, &val);
addEdge(u, v, -val);
}
if(!SPFA(1))
printf("YES\n");
else printf("NO\n");
}
return 0;
}