Bellman-Ford
Bellman-Ford的核心思想是松弛操作,对于边 ( u , v ) (u,v) (u,v),用 d i s t ( u ) dist(u) dist(u)和 l ( u , v ) l(u,v) l(u,v)的和尝试更新 d i s t ( v ) dist(v) dist(v):
d i s t ( v ) = m i n ( d i s t ( v ) , d i s t ( u ) + l ( u , v ) ) dist(v)=min(dist(v),dist(u)+l(u,v)) dist(v)=min(dist(v),dist(u)+l(u,v))
Bellman-Ford就是不断尝试对图上每一条边进行松弛操作。
进行多次迭代,每进行一次迭代,就对图上所有的边都尝试进行一次松弛操作,当一次迭代中没有点的dist发生改变时,算法停止。
先将除了起始点的距离赋值为无穷大,起始点赋值为0
在每次迭代中,遍历所有的边,尝试用“松弛操作”更新距离数组
int n, m, dist[N], pre[N], c[N];
struct Edge
{
int x, y, v;
} edge[M];
int shortestpath(int s, int t)
{
memset(dist, 127, sizeof dist);
memset(pre, 0, sizeof pre);
dist[s] = 0;
while (true)
{
bool flag = false;
for (int i = 1; i <= m; i++)
{
int x = edge[i].x;
int y = edge[i].y;
int v = edge[i].v;
if (dist[x] < 1 << 30)
if (dist[x] + v < dist[y])
{
dist[y] = dist[x] + v;
pre[y] = x;
flag = true;
}
}
if (!flag)
break;
}
//输出路径
if (dist[t] < 1 << 30)
{
int l = 0;
for (int i = t; i != s; i = pre[i])
c[++l] = i;
c[++l] = s;
for (int i = l; i; --i)
printf("%d ", c[i]);
puts("");
}
return dist[t];
}
int n, m;
int dist[N];
struct Edge
{
int a, b, w;
}edges[M];
int bellman_ford()
{
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
for (int i = 0; i < n; i ++ )
{
for (int j = 0; j < m; j ++ )
{
int a = edges[j].a, b = edges[j].b, w = edges[j].w;
if (dist[b] > dist[a] + w)
dist[b] = dist[a] + w;
}
}
if (dist[n] > 0x3f3f3f3f / 2) return -1;
return dist[n];
}
Bellman-Ford的优化
队列优化(SPFA)(能被卡到和Bellman-Ford一样的复杂度)
在每一次迭代时,只有在上一次迭代中被更新了距离的点,才有可能去更新其他节点,在每一次迭代时,我们将更新过距离的顶点加入一个队列(如果顶点已经在队列里则不加),在下一次迭代时只需要遍历队列中的顶点连出去的边即可。
typedef pair<int, int> PII;
vector<PII> edge[N];
int n, m, k, dist[M];
bool b[N];
queue<int> q;
void shortestpath(int s, int t)
{
memset(dist, 127, sizeof dist);
memset(b, false, sizeof b);
dist[s] = 0;
b[s] = true;
q.push(s);
while (!q.empty())
{
int x = q.front();
q.pop();
b[x] = false;
for (auto i : edge[x])
if (dist[x] + i.second < dist[i.first])
{
dist[i.first] = dist[x] + i.second;
if (!b[i.first])
{
q.push(i.first);
b[i.first] = true;
}
}
}
if (dist[t] < 1 << 30)
printf("%d\n", dist[t]);
else
printf("-1\n");
}
spfa判断负环
不需要初始化dist数组
原理:如果某条最短路径上有n个点(除了自己),那么加上自己之后一共有n+1个点,由抽屉原理一定有两个点相同,所以存在环。
typedef pair<int, int> PII;
int n;
vector<PII> edge[N];
int dist[N], cnt[N];
bool st[N];
// 存在负环返回true,否则返回false。
bool spfa()
{
queue<int> q;
for (int i = 1; i <= n; i++)
{
q.push(i);
st[i] = true;
}
while (!q.empty())
{
int x = q.front();
q.pop();
st[x] = false;
for (auto i : edge[x])
{
if (dist[i.first] > dist[x] + i.second)
{
dist[i.first] = dist[x] + i.second;
cnt[i.first] = cnt[x] + 1;
if (cnt[i.first] >= n)
return true;
if (!st[i.first])
{
q.push(i.first);
st[i.first] = true;
}
}
}
}
return false;
}