最短路
Floyd(全源最短路)
适用
复杂度较高,但常数较小,容易实现。
适用于 n≤500n \le 500n≤500 的情况。
概念
定义一个状态 dist[k][i][j]
,表示只允许使用编号从 1
到 k
的顶点作为中间点,从 i
到 j
的最短路径长度。
求解 dist[k][i][j]
,有两种选择:
- 不经过顶点
k
:直接由i
到j
。其路径直接为dist[k - 1][i][j]
- 经过顶点
k
:由i
到k
,再由k
到j
。其路径为dist[k - 1][i][k] + dist[k - 1][k][j]
。
则顶点 i
到 j
的最短路径长度为 dist[k][i][j] = min(dist[k - 1][i][j] dist[k - 1][i][k] + dist[k - 1][k][j];
代码模板:
for (int k = 1; k <= n; k ++) {
for (int i = 1; i <= n; i ++) {
for (int j = 1; j <= n; j ++) {
dist[k][i][j] = min(dist[k - 1][i][j] dist[k - 1][i][k] + dist[k - 1][k][j];
}
}
}
bitset<MAXN> f[MAXN];
void floyd_warshall() {
for (int k = 1; k <= n; k ++) {
for (int i = 1; i <= n; i ++) {
for (int j = 1; j <= n; j ++)
f[i][j] |= f[i][k] & f[k][j];
}
}
}
优化得 ↓
bitset<MAXN> f[MAXN];
void floyd_warshall() {
for (int k = 1; k <= n; k ++) {
for (int i = 1; i <= n; i ++) {
if (f[i][k])
f[i] |= f[k];
}
}
}
Bellman-Ford(单源最短路)
适用
可处理负权边,并能检测从源点可到达的负权环。
概念
核心原理:松弛操作
对于一条权重为 w
的边 (u, v)
,若从源点 s
-> v
的距离(dist[v]
)大于从s
-> u
的距离(dist[u]
)加上边(u, v)
的权重 w
,
即 dist[v] > dist[u] + w
, 就找到了一个经过 u
到达 v
的更短的路径。
则 可更新为 dist[v] = min(dist[v], dist[u] + w);
算法过程
不断尝试对图上的每一条边进行松弛操作,当一轮循环中没有成功の松弛操作,算法停止。
代码模板
const int INF = 0x3f3f3f3f;
struct edge_{
int u, v, w;
};
vector<edge_> edge; //存边
int dist[N], u, v, w;
bool bellman_ford(int n, int s) {
memset(dist, 0x3d, sizeof dist);
dist[s] = 0; //初始化
bool f = false;
for (int i = 1; i <= n; i ++) {
f = false;
for (int j = 0; j < edge.size(); j ++) {
u = edge[j].u, v = edge[j].v, w = edge[j].w;
if (dist[u] == INF) //不可达的起点
continue;
if (dis[v] > dis[u] + w) { //松弛操作
dis[v] = dis[u] + w;
f = true; //标记有更新
}
}
if (! f) //无更新,提前结束
break;
}
return f; //若第n轮仍有更新,则存在负环
}
优化-SPFA
太玄学了不写了
其实是来不及记了
过程
使用队列优化松弛操作,仅对距离更新的节点进行处理。
若节点被松弛超过vvv次,则存在负环。
时间复杂度为O(E)O(E)O(E),最坏为O(VE)O(VE)O(VE)。
Dijkstra(单源最短路)
适用
解决无负权边图上最短路问题の最常用高效の算法。
算法思路
Obviously,先初始化(dis[s] = 0
,其余元素为无穷大,vis[]
初始化为空)
迭代:
选取最小の节点 u
并将其标记为已访问。
对于 u
的每个节点 v
,若通过 u
到达 v
的路径更短(即 dis[u] + w < dis[v]
),更新dis[v]
。
代码模板(优化后)
老师写的抽象代码
这是老师写的代码但是其实我没看懂
void Dijkstra(int s) {
dis[s] = 0;
vector<int> vis(n + 1);
using pr = pair<ll, int>
struct Pair{
int u;
ll w;
bool operator<(const Pair &_) const {
return w > _.w;
}
};
priority_queue<Pair> Q;
Q.push({s, 0}); //小根堆
while (! Q.empty()) {
auto [u, now] = Q.top();
Q.pop();
if (vis[u])
continue;
for (auto [v, w] : E[u]) {
if (dis[v] > dis[u] + w) {
dis[v] = dis[u] + w;
Q.push({v, dis[u]});
}
}
}
}
所以我又去看了OIwiki的代码。。。
OI wikiの朴素实现:
struct edge_{
int v, w;
};
vector<edge_> edge[N];
int dis[N], vis[N];
void dijkstra(int n, int s) {
memset(dis, 0x3f, sizeof dis);
dis[s] = 0;
for (int i = 1; i <= n ; i++) {
int u = 0, mind = 0x3f3f3f3f;
for (int j = 1; j <= n; i++) {
if (! vis[j] && dis[j] < mind)
u = j, mind = dis[j];
vis[u] = true;
for (auto ed : e[u]) {
int v = ed.v, w = ed.w;
if (dis[v] > dis[u] + w)
dis[v] = dis[u] + w;
}
}
}
}
OI wikiの优先队列实现
struct edge_{
int v, w;
};
struct node{
int dis, u;
bool operator > (const node & a) const{
return dis > a.dis;
}
}; //优先队列中的节点结构体,用于优先队列的比较。
vector<edge_> e[N]; //邻接表存图
int dis[N], vis[N]; //dis最短距离,vis是否确定最短路径。
priority_queue<node, vector<node>, greater<node>> q;
void dijkstra(int n, int s) {
memset(dis, 0x3f, sizeof dis);
memset(vis, 0, sizeof vis); //初始化
dis[s] = 0;
q.push({0, s});
while (! q.empty()) {
int u = q.top().u; //距离最小の节点
q.pop();
if (vis[u]) //若已确定最短路径,则跳过
continue;
vis[u]= 1; //标记u为已确定の最短路
for (auto ed : e[u]) { //遍历邻接边
int v =ed.v, w = ed.w;
if (dis[v] > dis[u] + w) { //松弛操作
dis[v] = dis[u] + w;
q.push({dis[v], v}); //将更新后の节点v加入优先队列
}
}
}
}