Tarjan算法在求解边双连通分量(Edge-Biconnected Components, EBCC)和点双连通分量(Vertex-Biconnected Components, VBCC)时,核心思想类似,但处理细节和目标不同。以下是两段代码的详细解释及两者的主要区别:
边双连通分量(EBCC)代码解析
void tarjan(int u, int from) {
dfn[u] = low[u] = ++timestamp;
stk[++top] = u;
for (int i = h[u]; ~i; i = ne[i]) {
int j = e[i];
if (!dfn[j]) {
tarjan(j, i); // 递归访问子节点
low[u] = min(low[u], low[j]);
// 判断桥:若u到j是桥,则标记双向边
if (dfn[u] < low[j])
is_bridge[i] = is_bridge[i ^ 1] = true;
} else if (i != (from ^ 1)) { // 排除父边反向边
low[u] = min(low[u], dfn[j]);
}
}
// 找到边双根节点,弹出分量
if (dfn[u] == low[u]) {
++dcc_cnt;
int y;
do {
y = stk[top--];
id[y] = dcc_cnt;
} while (y != u);
}
}
核心逻辑
- 初始化:为当前节点
u
分配dfn
和low
,并入栈。 - 遍历邻接边:
- 若邻接点
j
未访问,递归处理,并用low[j]
更新low[u]
。若dfn[u] < low[j]
,则边u-j
为桥。 - 若邻接点
j
已访问且非父边反向边(避免环路误判),用dfn[j]
更新low[u]
。 - 弹栈分组:当
dfn[u] == low[u]
时,栈中从u
到栈顶的节点构成一个边双连通分量。
特点
- 排除父边:通过参数
from
跟踪父边,避免反向边干扰low
的计算。 - 桥的标记:桥的存在分割边双分量,每个分量内部无桥。
- 唯一归属:每个节点属于唯一分量。
点双连通分量(VBCC)代码解析
void tarjan(int u) {
dfn[u] = low[u] = ++timestamp;
stk[++top] = u;
if (u == root && h[u] == -1) { // 孤立点特判
dcc_cnt++;
dcc[dcc_cnt].push_back(u);
return;
}
int cnt = 0;
for (int i = h[u]; ~i; i = ne[i]) {
int j = e[i];
if (!dfn[j]) {
tarjan(j);
low[u] = min(low[u], low[j]);
// 发现割点条件
if (dfn[u] <= low[j]) {
cnt++;
if (u != root || cnt > 1) cut[u] = true; // 标记割点
// 弹出分量:从栈顶到j的节点构成一个点双
dcc_cnt++;
int y;
do {
y = stk[top--];
dcc[dcc_cnt].push_back(y);
} while (y != j);
dcc[dcc_cnt].push_back(u); // 割点u加入分量
}
} else {
low[u] = min(low[u], dfn[j]); // 无需排除父节点
}
}
}
核心逻辑
- 初始化:类似边双,但无需跟踪父边。
- 孤立点处理:单独处理孤立点为独立分量。
- 遍历邻接点:
- 递归处理未访问节点,更新
low[u]
。 - 若
dfn[u] <= low[j]
,说明u
为割点,弹出栈中节点到j
,构成一个点双,并将u
加入该分量。 - 割点标记:根据条件标记割点(根节点需至少两个子树)。
特点
- 允许父边影响:无需排除父节点,直接更新
low
。 - 动态弹栈:发现割点时立即弹出分量,割点保留在栈中以便其他分量使用。
- 共享割点:割点属于所有包含它的分量。
两者核心区别
特征 | 边双连通分量(EBCC) | 点双连通分量(VBCC) |
---|---|---|
目标 | 消除桥后剩余的极大连通子图 | 消除割点后剩余的极大连通子图 |
关键结构 | 桥(删除后图不连通) | 割点(删除后图不连通) |
分量关系 | 节点唯一属于一个边双 | 割点可属于多个点双 |
栈操作时机 | 仅在根节点(dfn[u] == low[u] )时弹栈 |
发现割点时多次弹栈,动态生成分量 |
父边处理 | 需排除父边反向边,避免错误更新low |
无需排除父边,直接更新low |
割点/桥的标记 | 标记桥,分割分量 | 标记割点,生成多分量 |
孤立点处理 | 自动包含在分量中 | 需显式判断孤立点为独立分量 |
关键总结
- 边双通过桥分割图,每个分量内部无桥,节点唯一。
- 点双通过割点分割图,割点被多个分量共享,动态弹栈生成分量。
- 边双需排除父边避免环路干扰,点双依赖子节点
low
值判断割点,无需处理父边。
拿y总模板问的DeepSeek