首页 > 其他分享 >图论——最短路 学习笔记

图论——最短路 学习笔记

时间:2023-11-15 12:22:52浏览次数:33  
标签:图论 松弛 int 短路 笔记 st mathcal dis

图论——最短路 学习笔记

其实是复习性质的,主要是总结,证明什么的,等上大学再说。

定义

  • 单源最短路:从一个点 \(q\) 出发,到其他所有点的最短路。
  • 全源最短路:任意两点见最短路。

算法对比

算法 Floyd Johnson Bellman–Ford SPFA Dijkstra
类型 全源 全源 单源 单源 单源
作用于 任意图 任意图 任意图 任意图 非负权图
检测负环 不能
时间复杂度 \(\mathcal{O}(n^3)\) \(\mathcal{O}(nm \log m)\) \(\mathcal{O}(nm)\) \(\mathcal{O}(m)\)-\(\mathcal{O}(nm)\) \(\mathcal{O}(n^2)\)-\(\mathcal{O}(m \log n)\)

总结:

  • 没有负环用 dijk,理论上,稠密图(有 \(m\) 与 \(n^2\) 同阶)用朴素的,稀疏图(有 \(m \ll n^2\) 的)用堆优化。
  • 有负环优先用 SPFA,即使她被卡也比 BF 快一点。
  • 多源用 Floyd,因为不会 Johnson。

Dijkstra

过程

设两个集合:「已确定最短路长度的集合 \(S\)」和「未确定最短路长度的集合 \(T\)」,每次从 \(T\) 中选取一个最近的,加入集合 \(S\) 并松弛,更新其他点的最短路。直到 \(T\) 集合为空。

代码:

int n, m, g[N][N];
array<int, N> dis, vis;	// 到这个点的最短路,是否已经确定最短路
int gett() {
	int t = -1;
	for (int i = 1; i <= n; ++j)
	if (!st[i] && (t == -1 || dis[t] > dis[i])) t = i;
	return t;
} void dijkstra(int s) {
	dis.fill(0x3f); dis[1] = 0;
	for (int i = 0; i < n; ++i) {
		int t = gett(); st[t] = true;
		for (int j = 1; j <= n; ++j)
		dist[j] = min(dist[j], dist[t] + g[t][j]);
	} return (void)("rp++");
}

时间复杂度:\(\mathcal{O}(n^2)\)。

堆优化

每成功松弛一条边 \((u,v)\) ,就将 \(v\) 插入堆中,如果 \(v\) 已经在堆中,直接修改相应元素的权值即可,每次查找操作直接取堆顶结点即可。

代码:

using pii = pair<int, int>;
array<int, N> dis, st;	// 到这个点的最短路,是否在堆中
#define v e[i]
void dijkstra(int s) {
	dis.fill(0x3f); dis[s] = 0;
	priority_queue<pii, vector<pii>, greater<pii>> heap;
	heap.push({0, s}); while (heap.size()) {
		pii t = heap.top(); heap.pop();
		int u = t.second, d = t.first;
		if (st[u]) continue; st[u] = true;
		for (int i = h[u]; i != -1; i = ne[i])
		if (dist[v] > d + w[i]) dis[v] = d + w[i], heap.push({dis[v], v});
	} return (void)("rp++");
}

共计 \(\mathcal{O}(m)\) 次二叉堆上的插入操作,\(\mathcal{O}(n)\) 次删除堆顶操作,而插入和删除的时间复杂度均为 \(\mathcal{O}(\log n)\),故时间复杂度:\(\mathcal{O}((m+n) \log n)=\mathcal{O}(m \log n)\)。

Bellman–Ford

不断尝试对图上每一条边进行松弛。我们每进行一轮循环,就对图上所有的边都尝试进行一次松弛操作,当一次循环中没有成功的松弛操作时,算法停止。

因为一个图的最短路,在没有负环的情况下,最长只能是 \(n-1\) 条边,所以松弛 \(n-1\) 轮即可。

因此可以得出此算法求不超过 \(k\) 条边的最短路的方法,即松弛 \(k\) 轮。

int n, m, k;
struct Edge { int a, b, w; } edges[M];
array<int, N> dis;
void bellman_ford(int s) {
	dis.fill(INF); dis[s] = 0;
	for (int i = 0; i < k; ++i) { // 不超过 k 条边的最短路
		auto bak = dis;	// 需要备份下来处理
		bool flag = false;
		for (int j = 0 ; j < m ; ++j)
		if (bak[edges[j].a] + edges[j].w < dis[edges[j].b])
			dis[edges[j].b] = bak[edges[j].a] + edges[j].w, flag = true;
		if (!flag) break;
	}
}

在最短路存在的情况下,由于一次松弛操作会使最短路的边数至少 \(+1\),而最短路的边数最多为 \(n-1\),因此整个算法最多执行 \(n-1\) 轮松弛操作。故总时间复杂度为 \(\mathcal O(nm)\)。

队列优化 Bellman–Ford——SPFA

SPFA 说的通俗点叫 Bellman–Ford 的队列优化(BFS)版。

原理是,只有上一次被松弛的结点,所连接的边,才有可能引起下一次的松弛操作。

那么我们用队列来维护「哪些结点可能会引起松弛操作」,就能只访问必要的边了。

代码:

int h[N], w[N], e[N], ne[N], idx;
array<int, N> dis, st;	// 到这个点的最短路,是否在队列中
#define v e[i]
void spfa(int s) {
	dis.fill(INF); dist[1] = 0;
	queue<int> q; q.push(1);
	st[1] = true;
	while (q.size()) {
		int u = q.front(); q.pop();
		st[u] = false;
		for (int i = h[u] ; i != -1 ; i = ne[i])
		if (dist[v] > dis[u] + w[i]) {
			dist[v] = dis[u] + w[i];
			if (!st[v]) q.push(v), st[v] = true;
		}
	}
}

栈优化 Bellman–Ford——找负环

通常用于判负环,不用像 SPFA 那样判进队次数的原因是:DFS 在栈内的只有祖先,而 BFS 有非祖先。

int dis[N], vis[N]; // 提前将 dis 赋为 INF
bool dfs_spfa(int u) {
	if (vis[u]) return true; vis[u] = true;
	for (int i = h[u]; i != -1; i = ne[i])
	if (dis[e[i]] > dis[u] + w[i]) {
		dis[e[i]] = dis[u] + w[i];
		if (dfs_spfa(e[i])) return true;
	} return vis[u] = false;
}

Floyd

一个很实用的全源最短路解法,特点是好写,容易拓展。

时间复杂度:\(\mathcal O(n^3)\)。

代码:

for (k = 1; k <= n; k++)
	for (x = 1; x <= n; x++)
		for (y = 1; y <= n; y++)
			f[x][y] = min(f[x][y], f[x][k] + f[k][y]);

Johnson

不会,现阶段不打算学。

Reference

[1] https://oi-wiki.org/graph/shortest-path/

标签:图论,松弛,int,短路,笔记,st,mathcal,dis
From: https://www.cnblogs.com/RainPPR/p/shortest-path.html

相关文章

  • 数据结构——字典树 学习笔记
    数据结构——字典树学习笔记字典树,也叫trie树。检索字符串本质是记录字符串前缀的一棵查找树,形态类似于:字典树使用边表示字母,节点表示一个前缀,同时也可以在节点上记录状态\(\mathit{tag}\)。基本实现形如:var: nex[0..siz][0..rng],idx est[0..siz],pre[0..siz]fun......
  • 偏序问题学习笔记
    前提给若干个\(n\)维的点,对于每个点求出每一维均小于等于它的点的数量。按字典序排序,然后预处理相同的点,这样后面的点不可能对前面的点产生贡献。如果某个点后面有与其相同的点,那么当前点的贡献就会少算,所以我们需要提前在当前点的答案中加上后面与其相同的点的数量。经过这......
  • 密码学笔记
    密码算法:对称密码算法、非对称密码算法、摘要算法对称密码算法:加密秘钥和解密秘钥相同的密码算法又称秘密秘钥算法或单秘钥算法分组密码算法(BlockCipher):块加密算法将明文拆分为N个固定长度的明文块用相同的秘钥和算法对每个明文块加密得到N个等长的密文块然后将N个......
  • uniapp开发笔记
    控件toast控件uni.showToast({icon:'none',title:'输入topic'})注意点引入图片需要的注意事项图片的宽度不能是auto相对路径和绝对路径绝对路径要以/开头示例代码{bigUrl:"static/image/img/Children.jpg",data:[{......
  • 34课笔记
     ......
  • openGauss学习笔记-123 openGauss 数据库管理-设置账本数据库-账本数据库概述
    openGauss学习笔记-123openGauss数据库管理-设置账本数据库-账本数据库概述123.1背景信息账本数据库融合了区块链思想,将用户操作记录至两种历史表中:用户历史表和全局区块表。当用户创建防篡改用户表时,系统将自动为该表添加一个hash列来保存每行数据的hash摘要信息,同时在blockc......
  • 《人机交互:以用户为中心的设计和评估》阅读笔记一
    人机交互学(humen-computerinteraction,HCI)是一门关于设计和评估以计算机为基础的系统而使这些系统能够最容易地为人类所使用的学科。以用户为中心的设计和评估的最基本思想就是将用户时时刻刻摆在所有过程的首位。在产品生命周期的最初阶段,产品的策略应当以满足用户的需求为基本......
  • 最短路径问题
    有权图的单源最短路算法Dijkstra算法给定任何一个非负权边的图$v_0,....v_n$,要找到\(v_0\)到\(v_m\)的最短路径。设已找到的最短路径的结点集合为\(S\),未找到最短路径的结点集合为\(T\),\(V_0\)到所有结点的最短路径数组为\(dist\),初始状态\(dist[0]=0,dist[1..n]=......
  • 《软件工程导论》读书笔记2
    在当今这个信息化时代,软件已经成为我们生活中不可或缺的一部分。从手机应用到大型系统,软件无处不在。为了更好地理解和掌握软件开发的过程和方法,我阅读了《软件工程导论》这本书。以下是我在阅读过程中的一些心得体会和收获。软件工程的定义和目标软件工程是一门研究如何有效......
  • CS224n笔记:word2vec(1)
    目录离散语义(discrete):分布语义(distribute):tokens、types分布的语言模型(distributionallanguagemodel):词嵌入模型Word2VecObjectivefunc(目标函数)Lossfunc(损失函数)P(O|C)和Softmax(x)P(O|C)的概率分布将损失函数展开求梯度公式损失函数的时间复杂度ChainRule:链......