单源最短路径 の 二叉堆优化的 Dijkstra
🚧🚧 SPFA 它死了 🚧🚧
可以通过构造数据使 SPFA 算法的时间复杂度退化到 \(O(NM)\) 和 Bellman-Ford 算法已经没什么区别了...QwQ
二叉堆优化的 Dijkstra
- 对于 无负边 的情况下可以达到 \(O(nlog(n))\) 且很难被卡
- Dijkstra是基于一种 贪心 的策略,首先用数组
dis
记录起点到每个结点的最短路径,再用一个数组保存已经找到最短路径的点
然后,从dis
数组选择最小值,则该值就是源点s
到该值对应的顶点的最短路径,并且把该点记为已经找到最短路
此时完成一个顶点,再看这个点能否到达其它点(记为v
),将dis[v]
的值进行更新
不断重复上述动作,将所有的点都更新到最短路径 - 这种算法实际上是 \(O(n^2)\) 的时间复杂度,但我们发现在
dis
数组中 选择最小值 时,我们可以用 小根堆 来进行优化。 - Dijkstra 每次进行 选择距离起点最近的顶点 需要 \(O(N)\) 的时间,这样每次都需要耗费很长时间选择, 使用 小根堆 进行优化,使每次选择时间降至 \(O(logN)\)
蒟蒻代码
// 二叉堆优化的 Dijkstra
#include <bits/stdc++.h>
#define re register
using namespace std;
typedef long long ll;
// 链式向前星
struct Edge{
int to; // x->当前节点的节点编号
int nxt; // x->当前节点对应的数组索引
int val; // 边权
};
// 优先队列元素
struct node{
int ver; // 顶点编号
ll dis; // 到 s 的距离, 用于小根堆排序
// 重载 < 运算符
bool operator <(const node& x) const{
return dis>x.dis; // 小根堆 所以是 >
}
};
const int N=1e5+5;
const int M=2e5+5;
int n,m,s;
// 存图
int head[N];
Edge edge[M];
int tot=0;
// 加边(起点, 终点, 边权)
void add(int x,int y,int w){
edge[++tot].to=y;
edge[tot].val=w;
edge[tot].nxt=head[x];
head[x]=tot;
}
bitset<N> vis;
// 优先队列内部实质是一个 小根堆
priority_queue<node> pq;
ll dis[N];
int main()
{
ios::sync_with_stdio(0);
clock_t c1 = clock();
#ifdef LOCAL
freopen("data.in","r",stdin);
freopen("data.out","w",stdout);
#endif
// ======================================================================
memset(dis,0x3f,sizeof(dis)); // 初始化极大值
cin>>n>>m>>s;
for(re int i=0;i<m;i++){
int a,b,c; cin>>a>>b>>c;
add(a,b,c);
}
// 算法主体
dis[s]=0; pq.push((node){s,0}); // s 入堆
while(!pq.empty()){ // 堆中还有可更新的元素
int x=pq.top().ver;
// int d=pq.top().dis; 不必取出, 仅用来给 priority_queue 排序
pq.pop();
// 标记蓝白点
if(vis[x]) continue;
vis[x]=1;
// 与 x 相邻的可更新的点全部入堆...怎么感觉有点SPFA的意思在里面
for(re int i=head[x]; i; i=edge[i].nxt){
int y=edge[i].to;
int w=edge[i].val;
if(dis[x]+w<dis[y]){
dis[y]=dis[x]+w;
pq.push((node){y,dis[y]}); // 入堆
}
}
}
for(int i=1;i<=n;i++) cout<<dis[i]<<" ";
// ======================================================================
end:
cerr << "Time Used:" << clock() - c1 << "ms" << endl;
return 0;
}
这里采用的是 懒惰删除 的思想。因为STL的优先队列不支持对内部元素的随机删除,所以采用一个标记。如果曾经使用过,那么在它到堆顶的时候就不使用它。相当于把删除操作延迟到堆顶进行。
一些东西
priority_queue
声明方法
priority_queue
内部是 二叉堆 , 本身有三种声明方法:
priority_queue<int> pq; //默认大根堆
priority_queue<int, vector<int>, less<int> > pq; // 大根堆
priority_queue<int, vector<int>, greater<int> > pq; // 小根堆
但一般 priority_queue
用自己定义的结构体类型时, 都要重载 <
运算符, 便于内部二叉堆排序比较
大根堆
// 优先队列元素
struct node{
int ver; // 顶点编号
ll dis; // 到 s 的距离, 用于小根堆排序
// 重载 < 运算符
bool operator <(const node& x) const{
return dis<x.dis; // 大根堆 所以是 <
}
};
priority_queue<node> pq; // 大根堆
小根堆
// 优先队列元素
struct node{
int ver; // 顶点编号
ll dis; // 到 s 的距离, 用于小根堆排序
// 重载 < 运算符
bool operator <(const node& x) const{
return dis>x.dis; // 小根堆 所以是 >
}
};
priority_queue<node> pq; // 小根堆