引入
连通图
在一个**无向图**$G$中,若从顶点$i$ 到顶点$j$有路径相连,则称 $i$和$j$是连通的。如果图中任意两点都是连通的,那么图被称作连通图。如果$G$是有向图,则称为强连通图(注意:需要双向都有路径)。如果是单向连通,则称$G$为单向连通图。
割点(关节点)
在无向连通图$G=(V,E)$中: 若对于$x\in 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; }