图论篇6——割点(关节点)
Posted czc1999
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了图论篇6——割点(关节点)相关的知识,希望对你有一定的参考价值。
引入
连通图
在一个**无向图**$G$中,若从顶点$i$ 到顶点$j$有路径相连,则称 $i$和$j$是连通的。如果图中任意两点都是连通的,那么图被称作连通图。如果$G$是有向图,则称为强连通图(注意:需要双向都有路径)。如果是单向连通,则称$G$为单向连通图。
割点(关节点)
在无向连通图$G=(V,E)$中: 若对于$xin V$, 从图中删去节点$x$以及所有与$x$关联的边之后, $G$分裂成两个或两个以上不相连的子图, 则称$x$为$G$的割点。 简而言之, 割点是无向连通图中的一个特殊的点, 删去中这个点后, 此图不再连通, 而所以满足这个条件的点所构成的集合即为割点集合。
割边(桥)
如果删除$G$的一条边$b$,图$G$分离成两个非空子图,则称边$b$为图$G$的桥。如下图中,顶点$u$和$v$都是割点,其他顶点都不是割点,边$(u,v)$是桥,其他边都不是桥。
关节点识别
方案一:DFS (O(n^2))
依次去掉每一个点,判断图是否还连通。
方案二: Tarjan 算法
DFS树
首先需要了解一些关于深度优先搜索树(DFS tree)的概念。
以下图为例:
它的深度优先搜索树如下:
其中黑色的边为树边:如果结点(u)因算法对边((u,v))的搜索而首次被发现,则((u,v))是一条树边。
简单点说就是,它是正常的一颗树的边,只看(1,2,3,4)结点,是一颗树。【箭头只是表示搜索顺序】
其中红色的边为反向边:方向边((u,v))是将结点(u)连接到其(dfs)树中的一个祖先结点(v)的边,环也被认为是反向边。
算法步骤
首先选定一个根节点,从该根节点开始遍历整个图(使用(DFS))。
对于根节点,判断是不是割点很简单,计算其子树数量就行,如果有2棵即以上的子树,就是割点。因为如果去掉这个点,这两棵子树就不能互相到达。
对于非根节点,判断是不是割点就有些麻烦了。我们维护两个数组(dfn[])和(low[])。
(dfn[u]):意思就是在(dfs)的过程中,当前的(u)结点是第几个(首次)被访问的。(之前一直不知道这个(dfn)是个什么缩写,豆腐脑?)
(low[u]):表示顶点(u)及其子树中的点,通过反向边,能够回溯到的最早的点((dfn)最小)的(dfn)值。
对于边((u, v)),如果(low[v]>=dfn[u]),此时(u)就是割点。
算法可视为线性时间复杂度,采用邻接表存储的话,应与(DFS)相同,为(O(V+E))
题目链接:https://www.luogu.org/problem/P3388
#include <iostream>
#include <set>
#include <stdio.h>
using namespace std;
int cnt, Time = 1;
int Low[20005], d[20005];
bool fuck[20005];
struct Node {
int data;
Node* next;
Node() {}
Node(int data) :data(data) {};
void push(int to) {
Node* s = new Node(to);
s->next = next;
next = s;
}
}head[20005];
void DFS(int u, int father) {
int cnt = 0;
Low[u] = d[u] = Time++;
Node* p = head[u].next;
while (p) {
int v = p->data;
if (d[v] == -1) {//如果v尚未访问
DFS(v, u);
if (Low[v] < Low[u])Low[u] = Low[v];
if (father != 0 && Low[v] >= d[u]) fuck[u] = true;
if (father == 0)
cnt++;
}
//如果v已经访问,但不是v的双亲,则v是一条反向边
Low[u] = Low[u] < d[v] ? Low[u] : d[v];
p = p->next;
}
if (father == 0 && cnt >= 2)fuck[u] = true;
}
int main() {
int i, n, m;
cin >> n >> m;
for (i = 1; i <= n; i++) {
d[i] = -1;
head[i].next = NULL;
}
for (i = 0; i < m; i++) {
int c1, c2;
//cin >> c1 >> c2;
scanf("%d%d", &c1, &c2);
head[c1].push(c2);
head[c2].push(c1);
}
for (int i = 1; i <= n; i++) {
if (d[i] == -1) DFS(i, 0);
}
//DFS(1, 0);
int res = 0;
for (int i = 1; i <= n; i++) {
if (fuck[i])res++;
}
cout << res << endl;
for (int i = 1; i <= n; i++) {
if (fuck[i])cout << i << ' ';
}
cout << endl;
return 0;
}
以上是关于图论篇6——割点(关节点)的主要内容,如果未能解决你的问题,请参考以下文章