一、Dijkstra
边权值都为正数。
- 稠密图g[][]——>朴素版
- 稀疏图h[],e[],ne[],ids——>堆优化
1.朴素版
算法实现:
849. Dijkstra求最短路 I - AcWing题库
2.堆优化版
关注优化在哪里了?
1)找最短距离点——>总共O(n·n)
2)加入【已选点集】——>总共O(1·n)
3)更新其他边——>总共O(m)
所以要对1)优化!可以用堆(查找最小元素O(1))这个数据结构。如下图所示:
算法实现:
850. Dijkstra求最短路 II - AcWing题库
8.12更:
背串了模板导致没AC,还是要深入理解。这个st[]数组确保已经确认过的点,不再更新这些点的邻边(因为可能存在重边或【堆里存了太多重复的一个点到其他边的距离,而这些距离又只比最小的大一点,比另一个点到其他边的距离 要小一些,导致夹在中间,这些数据是无意义的】,不continue掉,还会走下面的//update更新循环,导致mlogn中的n变为1.5e5 ^ 2,最终mlogn变为1e8+,报TLE)。
灵感来自:
二、Bellman-ford
边权值存在负数。
用的少,解决有边数限制的最短路。记住算法步骤,很好写实现代码。
算法实现:
三、spfa
边权值存在负数。
对bellman-ford的优化!过程类似。
时间复杂度一般:O(m),最坏:O(mn)无推导
算法实现:
小坑:队列不存储重复的【已变小的】点,所以要开st[]数组
含TLE的代码,没有考虑队列内的重复元素如下:
#include<iostream>
#include<cstring>
#include <queue>
using namespace std;
const int N = 200010;
int h[N], ne[N], e[N], w[N], ids;
int d[N];
bool st[N];
int n, m;
void add(int a, int b, int c)
{
e[ids] = b, ne[ids] = h[a], w[ids] = c, h[a] = ids++;
}
int spfa()
{
queue<int> q;
q.push(1);
d[1] = 0;
//st[1] = true;
while(q.size())
{
auto t = q.front(); q.pop();
//st[t] = false;
for(int i = h[t]; i != -1; i = ne[i])
{
int j = e[i];
if(d[j] > d[t] + w[i])
{
d[j] = d[t] + w[i];
q.push(j);
// if(!st[j])
// {
// st[j] = true;
// }
}
}
}
return d[n];
}
int main()
{
cin >> n >> m;
memset(h, -1, sizeof h);
memset(d, 0x3f, sizeof d);
while (m -- )
{
int a, b, w;
cin >> a >> b >> w;
add(a, b, w);
}
int t = spfa();
if(t > 0x3f3f3f3f / 2) cout << "impossible" << endl;
else cout << t << endl;
return 0;
}
正确处理队列重复元素(核心代码):
int spfa()
{
queue<int> q;//基于bellman-ford的无脑更新,我们用队列记住所有变小了的点
d[1] = 0;
//下面两句是捆绑的,num入队了就要对其进行st[]标识
q.push(1); st[1] = true;
while(q.size())
{
auto t = q.front();
q.pop(); st[t] = false; //捆绑
for(int i = h[t]; i != -1; i = ne[i])//队列里的都是已经变小了的点,所以都要进行d=min(d,.+.)操作
{
int j = e[i];
if(d[j] > d[t] + w[i])
{
d[j] = d[t] + w[i]; //因为j的d[]变小了,所以要入队
if(!st[j])
{
q.push(j); st[j] = true;
}
}
}
}
return d[n];
}
四、多源汇最短路——Floyd
for k: 1-n
for i: 1-n
for j: 1-n
g[i][j] = min(g[i][j], g[i][k] + g[k][j]);