推荐博客
朴素Dijkstra
时间复杂度为O(n^2)
核心代码:
for(int i=0; i<n; i++)
{
int t = -1;
for(int j=1; j<=n; j++) // 在没有确定最短路中的所有点找出距离最短的那个点 t
if(!st[j] && (t == -1 || dist[t] > dist[j]))
t=j;
st[t]=true; // 代表 t 这个点已经确定最短路了
for(int j=1; j<=n; j++) // 用 t 更新其他点的最短距离
dist[j] = min(dist[j],dist[t]+g[t][j]);
}
堆优化Dijkstra
时间复杂度O(nlogn+m)
模板:
#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
typedef pair<int,int> PII; //first存距离,second存结点编号
const int N = 2e5+10;
int n,m;
int h[N],w[N],e[N],ne[N],idx; // 邻接表的存储
int dist[N]; // 第 i 个点到起点的最短距离
bool st[N]; // 代表第 i 个点的最短路是否确定、是否需要更新
void add(int a,int b,int c)
{
e[idx] = b,w[idx] = c,ne[idx] = h[a],h[a] = idx++;
}
int dijkstra()
{
memset(dist,0x3f,sizeof(dist)); // 将所有距离初始化正无穷
dist[1] = 0; // 第一个点到起点的距离为 0
priority_queue<PII, vector<PII>, greater<PII>> heap; // 小根堆
heap.push({0,1}); //把 1 号点放入堆中
while(heap.size()) // 堆不空
{
PII t = heap.top(); //找到当前距离最小的点
heap.pop();
int ver = t.second,distance = t.first; // ver为编号,distance为距离
if(st[ver]) continue; // 重边的话不用更新其他点了
st[ver] = true; //标记 t 已经确定最短路
for(int i = h[ver]; i!=-1; i=ne[i]) // 用 t 更新其他点的最短距离
{
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; // 说明 1 和 n 是不连通的,不存在最短路
return dist[n];
}
int main()
{
memset(h,-1,sizeof(h));
cin >> n >> m;
while(m--)
{
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
add(a,b,c);
}
cout << dijkstra();
return 0;
}
Bellman-Ford
时间复杂度O(nm)
模板:
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 510,M = 10010;
int n,m,k;
int dist[N],backup[N]; //backup数组为上次
struct edges
{
int a,b,w; // a->b权值为w的边
}edge[M];
int bellman_ford()
{
memset(dist,0x3f,sizeof(dist));
dist[1] = 0;
for(int i=0; i<k; i++)
{
memcpy(backup,dist,sizeof(dist)); //备份
for(int j=0; j<m; j++) // 枚举所有边
{
int a = edge[j].a, b = edge[j].b, w=edge[j].w;
dist[b] = min(dist[b],backup[a]+w); // 用备份更新
}
}
if(dist[n] > 0x3f3f3f3f/2) return -1;
return dist[n];
}
int main()
{
cin >> n >> m >> k;
for(int i=0; i<m; i++)
{
int a,b,w;
cin >> a >> b >> w;
edge[i] = {a,b,w};
}
int t = bellman_ford();
if(t == -1) cout << "impossible";
else cout << t;
return 0;
}
SPFA
是对Bellman-Ford的优化,但是最坏情况下依然是O(nm)的时间复杂度。
模板:
int spfa()
{// bool st[N]: 存第 i 个点是不是在队列中,防止存重复的点
memset(dist,0x3f,sizeof(dist));
dist[1] = 0;
queue<int> q; //存储所有待更新的点
q.push(1); // 1号点入队
st[1] = true;
while(q.size()) // 队列不空
{
int t = q.front(); //取队头
q.pop();
st[t] = false; // 代表这个点已经不在队列了,因为存在边权为负数,某个点可能会被更新多次,所以可以多次入队和出队。
for(int i = h[t]; i!=-1; i=ne[i]) // 更新 t 的所有临边结点的最短路
{
int j = e[i];
if(dist[j] > dist[t]+w[i])
{
dist[j] = dist[t] + w[i];
if(!st[j]) //如果 j 不在队列,让 j 入队
{
q.push(j);
st[j] = true; // 标记 j 在队中
}
}
}
}
if(dist[n] == 0x3f3f3f3f) return -1; // 不存在最短路
return dist[n];
}
SPFA判断负环
用cnt数组记录每个节点最短路径的长度。
int spfa()
{
queue<int> q;
for(int i=1; i<=n; i++) //将所有结点入队
{
st[i] = true;
q.push(i);
}
while(q.size()) // 队列不空
{
int t = q.front(); //取队头
q.pop();
st[t] = false; // 代表这个点已经不在队列了
for(int i = h[t]; i!=-1; i=ne[i]) // 更新 t 的所有临边结点的最短路
{
int j = e[i];
if(dist[j] > dist[t]+w[i])
{
dist[j] = dist[t] + w[i];
cnt[j] = cnt[t] + 1; // t到起点的边数+1
if(cnt[j] >= n) return true;// 存在负环
if(!st[j]) //如果 j 不在队列,让 j 入队
{
q.push(j);
st[j] = true; // 标记 j 在队中
}
}
}
}
return false;// 不存在负环
}
Floyd
动态规划思想,正确性证明有点复杂,暂时不理会。
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]);
}