割点与桥(Tarjan)
cloud2764scallop_eve · · 算法·理论
\text{Tarjan} 求割点与桥
本来是打算总结 反正是不可能学完的,flag 随便立。
贴一个网站,Tarjan(知乎版:Tarjan),讲得相当不错,如果想直接一口吞掉
Enjoy the blog.
定义
如果有志向看非常复杂详细定义的,这里——OI-Wiki。
注意:割点和桥只在无向图中讨论。
割点
对于一个无向图,如果把一个点删除后这个图的极大连通分量数增加了,那么这个点就是这个图的割点(又称割顶)。 ——
\text{OI-Wiki}
设
如果
这时我们一般可以说
例如,上图中,
显然删除根节点后这两个分支将会互不相连。
如图,就是一般情况,易得
桥
和割点差不多的边,叫做桥。
对于一个无向图,如果删掉一条边后图中的连通分量数增加了,则称这条边为桥或者割边。严谨来说,就是:假设有连通图G=\{V,E\} ,e 是其中一条边(即e \in E ),如果G-e 是不连通的,则边e 是图G 的一条割边(桥)。 ——\text{OI-Wiki}
为了找到桥,我们要稍微修改一下
因为如果
上图中
如图,易得
实现过程
割点
暴力算法
对于暴力算法:如果我们尝试删除每个点,并且判断这个图的连通性,那么复杂度会特别的高。
暴力法的原理就是通过定义求解割点和割边。在图中去掉某个顶点,然后进行
\text{Tarjan} 算法
相对于暴力,
还是上文的图
首先,我们按照
这些信息被我们保存在
还需要另外一个数组
例如
然后我们开始
此根据惟独不适用于搜索的起始点,其需要特殊考虑:若该点不是割点,则其他路径亦能到达全部结点,因此从起始点只「向下搜了一次」,即在搜索树内仅有一个子结点。如果在搜索树内有两个及以上的儿子,那么他一定是割点了(设想上图从 2 开始搜索,搜索树内应有两个子结点:
我们在访问
代码:
int dfs[MAXN], low[MAXN], cnt;
vector<int> cut; // 存储所有割点
void tarjan(int p, bool root = true) {
int tot = 0;
low[p] = dfs[p] = ++cnt;
for (auto q : edges[p]) {
if (!dfs[q]) {
tarjan(q, false);
low[p] = min(low[p], low[q]);
tot += (low[q] >= dfs[p]); // 统计满足low[q] >= dfs[p]的子节点数目
} else
low[p] = min(low[p], dfs[q]);
}
if (tot > root) // 如果是根,tot需要大于1;否则只需大于0
cut.push_back(p);
}
桥
暴力算法
免谈。大抵是
过程
和割点差不多,只要改一处:
割边是和是不是根节点没关系的,原来我们求割点的时候是指点
代码
int low[MAXN], dfn[MAXN], dfs_clock;
bool isbridge[MAXN];
vector<int> G[MAXN];
int cnt_bridge;
int father[MAXN];
void tarjan(int u, int fa) {
father[u] = fa;
low[u] = dfn[u] = ++dfs_clock;
for (int i = 0; i < G[u].size(); i++) {
int v = G[u][i];
if (!dfn[v]) {
tarjan(v, u);
low[u] = min(low[u], low[v]);
if (low[v] > dfn[u]) {
isbridge[v] = true;
++cnt_bridge;
}
} else if (dfn[v] < dfn[u] && v != fa)
low[u] = min(low[u], dfn[v]);
}
}
当
isbridge[x]为真时,(father[x],x)为一条割边。
参考
Tarjan算法:求解图的割点与桥(割边) (详细的推导过程)
割点和桥 - OI Wiki
算法学习笔记(70): 割点和桥