Tarjan 算法笔记

Tarjan 系列算法笔记

0 概述

\(\text{Tarjan}\) 的核心思想:\(\text{dfn}\) 序与 \(\text{low}\) 数组

因此, \(\text{Tarjan}\) 的算法框架也是一个标准的 \(\text{dfs}\) 过程

\(\text{Juanzhang}\) 曾说过:

我到现在也没有理解 \(\text{Tarjan}\) 其中一些深奥的东西

希望我自己能够看到 \(\text{Juanzhang}\) 所看到的东西

笔记整理了如下算法:

  1. 割点与桥
  2. 点、边双联通分量
  3. 强联通分量
  4. 圆方树

1 dfn 与 low

1.1 dfn

\(\text{dfn}\) 数组所记录的是,该图中,在由一个点出发进行的 \(\text{dfs}\) 这个过程中,\(\text{dfn}\) 便为 \(\text{dfs}\) 到每一个点的时间戳 (由 \(1\) 开始)

简单记录下做题中用到的 \(\text{dfn}\) 的性质:

  1. \(\text{dfs}\) 的过程中,\(\text{dfn}\) 比某一个点的 \(\text{dfn}\) 大的点与该点的子树中的每一个点一一对应 (即可轻松判断是否在子树内)

在我看来,\(\text{dfn}\) 的意义在于,由于性质 \(1\),我们记录的 \(\text{dfn}\) 事实上暗含了一些信息,这些信息有如下作用:

  1. 给每一个点赋予了独一无二的编号
  2. 记录了一些关于子树的信息

1.2 low

\(\text{low}[\text{i}]\) 表示对于第 \(\text{i}\) 个节点,在 \(\text{dfs}\) 树上,以它为根的子树,除去它自己的所有节点的相邻节点的 \(\text{dfn}\) 的最小值

简单记录下做题中用到的 \(\text{low}\) 的性质:

  1. 容易想到,如果是一个树,那么这棵树上的任意一个节点,都有 \(\text{dfn}[\text{i}] = \text{low}[\text{i}]\)
  2. 但如果随便连一条本来不在树上的边 \((u, v)\)

2 割点与桥

割点的定义:

将该点割去后,即删除这个点和与之相邻的所有边之后,该图联通块数量增加的点称为割点

桥的定义:

将该边删除后,该图联通块数量增加的边称为边

转载于:https://www.cnblogs.com/blackwhitetony/p/11025810.html

Tarjan算法是一种用于查找有向图中的强连通分量(SCC)、无向图中的割点和桥的深度优先搜索算法。它的时间复杂度为 O(n+e),其中 n 是顶点数,e 是边数。下面是一个基于 C++ 的 Tarjan 算法实现示例,用于查找强连通分量(SCC)[^1]。 ### 强连通分量(SCC)的 Tarjan 算法实现 ```cpp #include <iostream> #include <vector> #include <stack> #include <algorithm> using namespace std; class Graph { private: int V; // 顶点数量 vector<vector<int>> adj; // 邻接表 vector<int> dfn; // 发现时间 vector<int> low; // 最早能追溯到的节点 stack<int> s; // 栈用于记录当前 SCC 的节点 vector<bool> inStack; // 节点是否在栈中 int time; // 时间戳 public: Graph(int V) : V(V), adj(V), dfn(V, -1), low(V, -1), inStack(V, false), time(0) {} void addEdge(int u, int v) { adj[u].push_back(v); } void tarjanSCC(int u) { dfn[u] = low[u] = ++time; s.push(u); inStack[u] = true; for (int v : adj[u]) { if (dfn[v] == -1) { // 如果未被访问过 tarjanSCC(v); low[u] = min(low[u], low[v]); } else if (inStack[v]) { // 如果在栈中,说明是回边 low[u] = min(low[u], dfn[v]); } } // 如果是根节点,开始弹出栈中的节点,形成 SCC if (dfn[u] == low[u]) { cout << "SCC: "; while (true) { int v = s.top(); s.pop(); inStack[v] = false; cout << v << " "; if (v == u) break; } cout << endl; } } void findSCCs() { for (int i = 0; i < V; i++) { if (dfn[i] == -1) { tarjanSCC(i); } } } }; int main() { Graph g(5); g.addEdge(0, 1); g.addEdge(1, 2); g.addEdge(2, 0); g.addEdge(1, 3); g.addEdge(3, 4); cout << "Strongly Connected Components:" << endl; g.findSCCs(); return 0; } ``` ### 割点的 Tarjan 算法实现 ```cpp #include <iostream> #include <vector> #include <algorithm> using namespace std; class Graph { private: int V; // 顶点数量 vector<vector<int>> adj; // 邻接表 vector<int> dfn; // 发现时间 vector<int> low; // 最早能追溯到的节点 vector<bool> isCut; // 是否是割点 int time; // 时间戳 public: Graph(int V) : V(V), adj(V), dfn(V, -1), low(V, -1), isCut(V, false), time(0) {} void addEdge(int u, int v) { adj[u].push_back(v); adj[v].push_back(u); // 无向图需要双向边 } void tarjanCutPoint(int u, int parent) { dfn[u] = low[u] = ++time; int children = 0; // 子节点数量 for (int v : adj[u]) { if (v == parent) continue; // 跳过父节点 if (dfn[v] == -1) { // 如果未被访问过 children++; tarjanCutPoint(v, u); low[u] = min(low[u], low[v]); // 如果 u 是根且有多个子节点,则 u 是割点 if (parent == -1 && children > 1) { isCut[u] = true; } // 如果 u 不是根且 low[v] >= dfn[u],则 u 是割点 if (parent != -1 && low[v] >= dfn[u]) { isCut[u] = true; } } else { // 回边 low[u] = min(low[u], dfn[v]); } } } void findCutPoints() { for (int i = 0; i < V; i++) { if (dfn[i] == -1) { tarjanCutPoint(i, -1); } } cout << "Cut Points: "; for (int i = 0; i < V; i++) { if (isCut[i]) { cout << i << " "; } } cout << endl; } }; int main() { Graph g(5); g.addEdge(0, 1); g.addEdge(0, 2); g.addEdge(1, 3); g.addEdge(2, 3); g.addEdge(1, 4); cout << "Finding Cut Points:" << endl; g.findCutPoints(); return 0; } ``` ### 桥的 Tarjan 算法实现 ```cpp #include <iostream> #include <vector> #include <algorithm> using namespace std; class Graph { private: int V; // 顶点数量 vector<vector<int>> adj; // 邻接表 vector<int> dfn; // 发现时间 vector<int> low; // 最早能追溯到的节点 int time; // 时间戳 public: Graph(int V) : V(V), adj(V), dfn(V, -1), low(V, -1), time(0) {} void addEdge(int u, int v) { adj[u].push_back(v); adj[v].push_back(u); // 无向图需要双向边 } void tarjanBridge(int u, int parent) { dfn[u] = low[u] = ++time; for (int v : adj[u]) { if (v == parent) continue; // 跳过父节点 if (dfn[v] == -1) { // 如果未被访问过 tarjanBridge(v, u); low[u] = min(low[u], low[v]); // 如果 low[v] > dfn[u],则 u-v 是桥 if (low[v] > dfn[u]) { cout << "Bridge: " << u << "-" << v << endl; } } else { // 回边 low[u] = min(low[u], dfn[v]); } } } void findBridges() { for (int i = 0; i < V; i++) { if (dfn[i] == -1) { tarjanBridge(i, -1); } } } }; int main() { Graph g(5); g.addEdge(0, 1); g.addEdge(0, 2); g.addEdge(1, 3); g.addEdge(2, 3); g.addEdge(1, 4); cout << "Finding Bridges:" << endl; g.findBridges(); return 0; } ``` ### 总结 以上代码分别实现了 Tarjan 算法用于查找强连通分量(SCC)、割点和桥。每个算法的核心思想都是基于深度优先搜索(DFS),通过维护 `dfn` 和 `low` 数组来判断节点之间的连接关系。这些算法的时间复杂度为 O(n+e),非常适合处理大规模图数据。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值