限制:bellman_ford 和 spfa 可用于判断负环, 其他算法只能用于无负环情况。
朴素dijkstra算法
(ps: 因为时稠密图,我们用邻接矩阵来存图)
设定dist[i]为点i到1的最短距离
st[i]为该点的最短距离是否已经确定
假设我们要求点1到点n的最短距离
先将dist数组初始化为*INF*,*dist[1] = 0*;
循环n次
每次取出所有点中dist最小的点t,并将其标记st[t] = true
通过邻接矩阵用点t来更新其他的点(dist[j] = min(dist[j], dist[t] + g[t][j]))
由于每次可以确定一个点的dist
循环n次可以确定所有点的dist
最终返回dist[n]即可,如果可能不存在最短路径就判断是否为INF
总结:每次取出未确定dist点中dist最小的点更新其他点
代码:
int dijkstra()
{
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
for(int i = 0; i < n; i++)
{
int t = -1;//还未确定最短路的点中距离最小的
for(int j = 1; j <= n; j++)
{
if(!st[j] && (t == -1 || dist[t] > dist[j]))//更新t
t = j;
}
st[t] = true;
//利用t更新其他的点
for(int j = 1; j <= n ; j++)
dist[j] = min(dist[j], dist[t] + g[t][j]);
}
if(dist[n] == 0x3f3f3f3f) return -1;
return dist[n];
}
复杂度分析:
两重n次循环(o(n^2))
堆优化版的dijkstra算法
优化思路:
由于是稀疏图,我们使用邻接表储存图,
每次取出st==false的dist最小值的点, 使用优先队列(堆)来储存
每次更新从堆中取出点有连边的点
总结:
用堆和邻接表优化
代码:
int dijkstra()
{
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
priority_queue<PII, vector<PII>, greater<PII>> heap;
heap.push({0, 1});//first储存距离,second储存节点编号
while(heap.size())
{
auto t = heap.top();//取距离最小的点
heap.pop();
int ver = t.second, distance = t.first;
if(st[ver]) continue;
st[ver] = true;
//依次更新连接ver的点
for(int i = h[ver]; i != -1; i = nex[i])
{
//如果可以更新就更新距离,并推入堆中
int j = e[i];
if(dist[j] > distance + w[i])
{
dist[j] = distance + w[i];
heap.push({dist[j], j});
}
}
}
if(dist[n] == 0x3f3f3f3f) return -1;
return dist[n];
}
复杂度分析:
遍历每一条边m次,每次推入堆中logn,总的mlogn
Bellman_Ford算法
算法思路:
循环k次(经过不超过k条边的最短距离)
每次循环遍历所有的边(设边的起点为a, 终点为b, 权值为w),更新dist[b] = min(dist[b], dist[a] + w)
所以我们可以直接用struct存边
存在问题:
所以我们可以创建一个backu数组储存上一次dist的结果,用上一次的结果来更新
然后由于负权边的存在,最后的答案可能会比INF小一点
代码:
int Bellman_Ford()
{
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
for(int i = 1; i <= k ; i++)//经过不超过k条边的最短距离
{
memcpy(backup, dist, sizeof dist);
for(int j = 0; j < m; j++)
{
int a = edges[j].a, b = edges[j].b, w = edges[j].w;
dist[b] = min(dist[b], backup[a] + w);//防止串连
}
}
if(dist[n] > 0x3f3f3f3f/2) return -1;//存在负权边导致初始化的值有可能变小
return dist[n];
}
如何判断负环:
如果k = n时仍有更新情况,则存在负环
spfa算法
spfa其实是bellman_floyd的一个优化
算法思路:
由dist[b] = min(dist[b], backup[a] + w)可知,当a的dist发生变化时b才需要更新
同样地,我们依旧可以用邻接表储存边,并用队列维护dist发生了改变的点,每次取出队头元素更新连接的点。
代码:
int spfa()
{
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
queue <int> q;
q.push(1);
st[1] = true;
while(q.size())
{
int t = q.front();
q.pop();
st[t] = false;
for(int i = h[t]; i != -1; i = nex[i])
{
int j = e[i];
if(dist[j] > dist[t] + w[i])
{
dist[j] = dist[t] + w[i];
if(!st[j])
{
q.push(j);
st[j] = true;
}
}
}
}
//if(dist[n] == 0x3f3f3f3f) return -1; 防止最短路径就是-1,将此行删除
return dist[n];
}
如何判断负环:
将所有点的dist都初始化为0,因为可能存在负环时1到不了的
cnt[x]存储1到x的最短路中经过的点数(除开自己)
然后如果存在cnt[x] >= n那么有负环
代码:
bool spfa()
{
queue <int> q;
for(int i = 1; i <= n ; i++)
{
q.push(i);
st[i] = true;
}
while(q.size())
{
int t = q.front();
q.pop();
st[t] = false;
for(int i = h[t]; i != -1; i = nex[i])
{
int j = e[i];
if(dist[j] > dist[t] + w[i])
{
dist[j] = dist[t] + w[i];
cnt[j] = cnt[t] + 1;
if(cnt[j] >= n) return true;
if(!st[j])
{
q.push(j);
st[j] = true;
}
}
}
}
return false;
}
floyd算法
直接看题目把,floyd算法背过就好了
用d数组储存边的情况,然后背模板输出就好了
#include <bits/stdc++.h>
using namespace std;
const int N = 210, INF = 1e9;
int d[N][N];
int n, m, q;
void floyd()
{
for(int k = 1; k <= n; k++)
for(int i = 1; i <= n; i++)
for(int j = 1; j <= n; j++)
d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
}
int main()
{
scanf("%d%d%d", &n, &m, &q);
for(int i = 1; i <= n; i++)
for(int j = 1; j <= n ;j++)
if(i == j) d[i][j] = 0;
else d[i][j] = INF;
while(m--)
{
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
d[a][b] = min(d[a][b], c);
}
floyd();
while(q--)
{
int a, b;
scanf("%d%d", &a, &b);
if(d[a][b] >= INF/2) printf("impossible\n");
else printf("%d", d[a][b]);
}
return 0;
}
acwing上的各种模板题:
朴素dijkstra
堆优化dijstra
bellman_ford
spfa
spfa判负环
floyd