Pixiv - Miracle
9-图论
1291 字
6 分钟
9-图论
图论
图论基础
无论无向图还是有向图
一般用vector<vector<int>> g; g.resize(n + 1, vector<int>());
多源最短路径的时候用邻接矩阵,只有树的建图是特殊的
树就是无环连通图,无环图就是森林
- 连通且无环
- 连通且边数为 n−1, 防止孤立点导致剩下的有环
- 无环且边数为 n−1,防止孤立点导致不连通
- 任意两个顶点之间有且仅有一条简单路径
Huffman树
最小的值放越下面,越大越接近根结点 取两个值最小的结点放最下面并形成根节点为其和
AOE网及关键路径
//TODO
回溯
循环可能的取值时对可能的取值进行判断和初始化相关控制变量,并在最开始的时候特判和初始化相关控制变量,如果进去说明判断成功,故终止条件是实指针
最短路
单源最短路径
朴素Dijkstra是初始化确认源点并松弛其出边, 然后每次确认尚未确认的点中dist最小的点并松弛其出边,堆优化Dijkstra是优先队列BFS,Bellman-ford是n次松弛通过出度松弛dist
Dijkstra
设:
- 当前从堆里取出的点是
u - 此时
dist[u]是所有未确定点中最小的
假设 存在一条更短的路径:
s → ... → x → ... → u,长度 < dist[u]
其中:
x是这条路径上 第一个尚未被确定的点x之前的点都已经被确定了
那么:
-
到
x的真实最短距离 -
而 Dijkstra 一定已经在处理
x的前驱时:- 尝试过松弛
x - 所以
dist[x]应该已经 ≤ 这个真实最短值
- 尝试过松弛
-
于是得到:
-
矛盾出现了
- 既然
dist[x] < dist[u] - 那
x应该比u更早从堆里弹出 - 不可能轮到
u
- 既然
简而言之,假设a+b<c,b > 0,则有a < c - b < c,与 c < a矛盾,故不需比对已经确认的点,可以将最短遍历路上的其他点标为确认,dijkstra每次选最短的边是从已经遍历的点的所有出边里面选,可看作为小根堆,扩展了则出边出堆。
- 朴素
// 朴素Dijkstraint dijkstra(int s, int n) { memset(dist, 0x3f, sizeof dist); dist[s] = 0;
for (int k = 0; k < n - 1; ++k) { int t = 0; for (int i = 1; i <= n; ++i) if (!st[i] && (!t || dist[i] < dist[t])) t = i; for (int i = 1; i <= n; ++i) dist[i] = min(dist[i], dist[t] + g[t][i]); st[t] = 1; }
return dist[n] == INF ? -1 : dist[n];}- 堆优化
// 堆优化Dijkstraint Dijkstra(int s, int n) { memset(dist, 0x3f, sizeof dist); dist[s] = 0;
priority_queue<PII, vector<PII>, greater<PII>> q; q.push({0, s});
while(q.size()) { PII t = q.top(); int ver = t.second, dis = t.first; if (st[ver]) continue;st[ver] = 1;
for (int i = h[ver]; i != -1; i = ne[i]) { int j = e[i]; if (dist[j] > dist[ver] + w[i]) { dist[j] = dist[ver] + w[i]; q.push({dist[j], j}); } } }
return dist[n] == INF ? -1 : dist[n];}Bellmanford
// Bellmanfordint Bellmanford(int s, int n, int m) { memset(dist, 0x3f, sizeof dist); dist[s] = 0;
for (int k = 0; k < n; ++k) { for (int i = 1; i <= n; ++i) { int a = i; for (int j = h[i]; j != -1; j = ne[j]) { int b = e[j]; dist[b] = min(dist[b], dist[a] + w[i]); } } }
return dist[n] > dist[n] >> 1 ? -1 : dist[n];}任意两点最短路径
Floyd
适合稠密图找任意两点的最短路径
void floyd(int n) { for (int k = 1; k <= n; ++k) { for (int i = 1; i <= n; ++i) { for (int j = 1; j <= n; ++j) { d[i][j] = min(d[i][j], d[i][k] + d[k][j]); } } }}最小生成树
Prim
找未遍历过的最近点
Kruscal
找最短的连接不同连通分量的边
至多 m - 2 次就能合并成功
BFS
多源BFS
多源BFS能遍历所有点的最小步骤数量取决于图里最长的一条路径,步数等于路径长度
DFS
- 自顶向下的DFS为回溯
- 自底向上的DFS为DP 两种方向可以同时存在,因为这个方向说的是DFS函数帧里的参数,包括使用到的全局变量和函数参数
Tarjan
求强连通分量
const int N = 100005;
vector<int> g[N];
int dfn[N], low[N], timestamp;int stk[N], top;bool in_stk[N];
int scc_id[N], scc_cnt;
void tarjan(int u) { dfn[u] = low[u] = ++timestamp; stk[++top] = u; in_stk[u] = true;
for (int v : g[u]) { if (!dfn[v]) { tarjan(v); low[u] = min(low[u], low[v]); } else if (in_stk[v]) { low[u] = min(low[u], dfn[v]); } }
// 找到一个 SCC if (dfn[u] == low[u]) { ++scc_cnt; int y; do { y = stk[top--]; in_stk[y] = false; scc_id[y] = scc_cnt; } while (y != u); }}Tarjan 求割点(无向图)
vector<int> g[N];int dfn[N], low[N], timestamp;bool is_cut[N];
void tarjan(int u, int parent) { dfn[u] = low[u] = ++timestamp;
int child = 0;
for (int v : g[u]) { if (!dfn[v]) { tarjan(v, u); low[u] = min(low[u], low[v]);
// 割点判断 if (parent != -1 && low[v] >= dfn[u]) is_cut[u] = true;
child++; } else if (v != parent) { low[u] = min(low[u], dfn[v]); } }
// 根节点特殊判断 if (parent == -1 && child > 1) is_cut[u] = true;}求桥
struct Edge { int to, id;};
vector<Edge> g[N];int dfn[N], low[N], timestamp;bool is_bridge[N];
void tarjan(int u, int in_edge) { dfn[u] = low[u] = ++timestamp;
for (auto [v, id] : g[u]) { if (!dfn[v]) { tarjan(v, id); low[u] = min(low[u], low[v]);
// 桥判断 if (low[v] > dfn[u]) is_bridge[id] = true; } else if (id != in_edge) { low[u] = min(low[u], dfn[v]); } }}文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!
随机文章 随机推荐