mirror of
https://gitcode.com/Zengtudor/alg2025.git
synced 2025-10-17 21:42:25 +00:00
feat: 实现Tarjan算法查找点双连通分量
添加Tarjan算法实现来查找图中的点双连通分量(v-BCC)。包括处理孤立节点和自环边的情况,并输出每个v-BCC的节点列表。使用手动栈优化性能,并添加详细注释说明算法逻辑。
This commit is contained in:
parent
1f4768391d
commit
9a06eb0821
@ -1,9 +1,100 @@
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
|
||||
using ll = int64_t;
|
||||
const int MAXN = 5e5+5; // 节点数上限
|
||||
|
||||
int n, m;
|
||||
std::vector<int> adj[MAXN]; // 邻接表存图
|
||||
|
||||
int dfn[MAXN], low[MAXN], ts; // Tarjan算法所需的时间戳数组
|
||||
int stk[MAXN], top; // 手动实现的栈,比std::stack稍快
|
||||
bool isbcc[MAXN]; // 标记节点是否已属于某个v-BCC
|
||||
|
||||
int main(){
|
||||
std::vector<std::vector<int>> bcc; // 存储所有找到的点双连通分量
|
||||
|
||||
void tarjan(int u, int parent) {
|
||||
dfn[u] = low[u] = ++ts;
|
||||
stk[++top] = u;
|
||||
|
||||
// 对于孤立点,若没有边,则tarjan函数会直接结束。
|
||||
// 我们将在main函数中处理这种情况。
|
||||
|
||||
for (int v : adj[u]) {
|
||||
// 在无向图中,(u,v)和(v,u)是同一条边,避免访问刚过来的边
|
||||
if (v == parent) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!dfn[v]) { // v未被访问,是u在DFS树中的子节点
|
||||
tarjan(v, u);
|
||||
low[u] = std::min(low[u], low[v]);
|
||||
|
||||
// 关键判断:v及其子树无法到达u的祖先
|
||||
if (low[v] >= dfn[u]) {
|
||||
std::vector<int> curbcc;
|
||||
int node;
|
||||
// 从栈中弹出节点,直到v被弹出
|
||||
do {
|
||||
node = stk[top--];
|
||||
curbcc.push_back(node);
|
||||
isbcc[node] = true;
|
||||
} while (node != v);
|
||||
|
||||
// 割点u也属于这个v-BCC
|
||||
curbcc.push_back(u);
|
||||
isbcc[u] = true;
|
||||
|
||||
bcc.push_back(curbcc);
|
||||
}
|
||||
} else { // v已被访问,(u,v)是返祖边
|
||||
low[u] = std::min(low[u], dfn[v]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int main() {
|
||||
std::ios_base::sync_with_stdio(false);
|
||||
std::cin.tie(nullptr);
|
||||
|
||||
std::cin >> n >> m;
|
||||
|
||||
for (int i = 0; i < m; ++i) {
|
||||
int u, v;
|
||||
std::cin >> u >> v;
|
||||
// 题目允许重边和自环。对于v-BCC,重边和一条边效果一样,自环可特殊处理
|
||||
// 此处我们正常建图,让tarjan处理
|
||||
if (u == v) continue; // v-BCC定义通常不考虑自环,但题目样例有,先忽略自环边,最后处理孤立点
|
||||
adj[u].push_back(v);
|
||||
adj[v].push_back(u);
|
||||
}
|
||||
|
||||
// 遍历所有节点,确保处理完所有连通分量
|
||||
for (int i = 1; i <= n; ++i) {
|
||||
if (!dfn[i]) {
|
||||
// 如果一个节点是孤立的(或只与已访问过的部分相连),
|
||||
// tarjan调用后栈可能为空或只含自己。
|
||||
// 为了简化,我们只在tarjan内部处理连通的组件。
|
||||
tarjan(i, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// 最后的清扫:处理所有未被分配到任何v-BCC的节点
|
||||
// 这些通常是孤立点,或者图中只有一个节点的情况,包括有自环的孤立点
|
||||
for (int i = 1; i <= n; ++i) {
|
||||
if (!isbcc[i]) {
|
||||
bcc.push_back({i});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 输出结果
|
||||
std::cout << bcc.size() << "\n";
|
||||
for (const auto& ret : bcc) {
|
||||
std::cout << ret.size();
|
||||
for (int node : ret) {
|
||||
std::cout << " " << node;
|
||||
}
|
||||
std::cout << "\n";
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user