创作背景
SMZX十日游倒数第二天,同时最短路径也是我的噩梦,所以……
目录
文章目录
前置技能
什么是最短路径
最短路径求的是从某点到另一个点的最短距离(废话)
松弛操作
设当前得到的距离数组是d
对一条从u到v、权值为w的边进行
松弛即d[v]=min(d[v],d[u]+w);
Dijkstra(贪心算法)
简介
Dijkstra可以求从一个点(称为源S)出发,到各个点的最短距离
这种问题称之为单源最短路径问题
Dijkstra是一个贪心算法,只能处理边权非负的图
算法流程
- 首先S到S的距离d[S]=0
- 从未访问过的点中选取距离最短的点u,即d[u]最小
- 将u的所有出边松弛一遍,即把u能到的点的最短距离尝试更新一遍
- 此时S到u的最短距离已经求出,即d[u],继续重复前两步操作
模拟算法
-
选取点1
-
第一遍松弛
-
选取点2
-
第二遍松弛
-
选取点4并松弛
-
选取点3并松弛
-
完成!
Code
int dijkstra(int S)
{
for (int i = 1; i <= n; i++)
dis[i] = 1e9, vis[i] = false;
dis[S] = 0;
for (int i = 1; i < n; i++) {
int u = -1;
for (int j = 1; j <= n; j++)
if (!vis[j] && (u == -1 || dis[j] < dis[u]))
u = j;
vis[u] = true;
for (int j = 0; j < e[u].size(); j++) {
int v = e[u][j].to, w = e[u][j].dis;
if (!vis[v] && dis[u] + w < dis[v])
dis[v] = dis[u] + w;
}
}
}
优化思路
但是呢,这里时间复杂度太高,达到了O(n^2m)原因在于重复了多遍松弛操作,浪费时间,所以就需要用一样东西让它只做一遍后将结果存好,于是:
堆
/
线
段
树
/
优
先
队
列
优
化
!
!
堆/线段树/优先队列优化!!
堆/线段树/优先队列优化!!
Code
int dijkstra2(int S)
{
for (int i = 1; i <= n; i++)
dis[i] = 1e9, vis[i] = false;
dis[S] = 0;
priority_queue<PR, vector<PR>, greater<PR> > q;
q.push(PR(dis[S], S));
while (!q.empty()) {
int u = q.top().second;
q.pop();
if (vis[u]) continue;
vis[u] = true;
for (int j = 0; j < e[u].size(); j++) {
int v = e[u][j].to, w = e[u][j].dis;
if (!vis[v] && dis[u] + w < dis[v]) {
dis[v] = dis[u] + w;
q.push(PR(dis[v], v));
}
}
}
}
Bellman-Ford(暴力)
算法流程:
不断尝试松弛,直到不能松弛为止(这不就是枚举暴力吗)
可以证明,最多n-1次循环即可得到最短路径
若第n次循环还能进行松弛操作,说明有负环
模拟算法
额……这么暴力的算法,需要模拟吗?
Code
for (int i = 1; i <= n; i++)
dis[i] = 1e9;
dis[S] = 0;
for (int i = 1; i < n; i++) {
for (int u = 1; u <= n; u++)
for (int j = 0; j < e[u].size(); j++)
dis[e[u][j].to] = min(dis[e[u][j].to], dis[u] + e[u][j].dis);
}
优化思路
这么暴力的算法,肯定慢啊,为什么?
假如d[u]在上一轮循环中没有更新
d[v] = min(d[v], d[u] + w)
d[v]不会被更新
因此从u出发的边没有必要进行松弛操作
怎么办?它的进阶版就出现了:
S
P
F
A
SPFA
SPFA
SPFA每次只把成功更新过的点放入队列,然后选择队头更新其它点。
Code
int SPFA(int S)
{
for (int i = 1; i <= n; i++)
dis[i] = 1e9, vis[i] = false;
dis[S] = 0;
queue<int> q;
q.push(S);
vis[S] = true;
while (!q.empty()) {
int u = q.front();
q.pop();
vis[u] = false;
for (int j = 0; j < e[u].size(); j++) {
int v = e[u][j].to, w = e[u][j].dis;
if (dis[u] + w < dis[v]) {
dis[v] = dis[u] + w;
if (!vis[v]) {
q.push(v);
vis[v] = true;
}
}
}
}
}
Floyd(DP)
简介
这个算法能解决多源最短路径问题,即可以求任意两个点之间的最短距离
关键是这个算法非常好写
当然时间复杂度也比较高O(n^3)
算法流程
设f[i][j][k]为从i到j,路径只途经编号为1~k的结点的最短路径
转移f[i][j][k]=min(f[i][k][k-1]+f[k][j][k-1],f[i][j][k-1]
初始化f[i][j][0]为从i到j的边权,没有边就无穷大
Code
int floyd()
{
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
f[i][j] = 0; // i~j的边权
for (int k = 1; k <= n; k++) // 最外层循环是k
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
f[i][j] = min(f[i][j], f[i][k] + f[k][j]);
}
The End
第
十
篇
博
客
纪
念
第十篇博客纪念
第十篇博客纪念
很快夏令营就结束了,所以……当然是准备收官之战啊