Tarjan求割点与桥
参考博客:Tarjan算法(中):求割点与桥
割点与桥的概念
割点:
在一个无向图中,如果有一个顶点集合,删除这个顶点集合以及这个集合中所有顶点相关联的边以后,图的连通分量增多,就称这个点集为割点集合。一个结点称为割点(或者割顶)当且仅当去掉该节点极其相关的边之后的子图不连通。
简单来说就是:
这些点维持着图的连通性,去掉这些点,这个连通分量就无法在维持下去,分成好几个连通分量。
桥:
一条边称为桥(或者割边)当且仅当去掉该边之后的子图不连通。
求割点
算法思想:
首先选定一个根节点,从该根节点开始遍历整个图(使用DFS)
一、对于根节点
- 如果有2棵即以上的子树,就是割点(因为如果去掉这个点,这两棵子树就不能互相到达)
二、对于非根节点
-
如果节点 U 的所有孩子节点可以不通过父节点 U 而访问到 U 的祖先节点,那么说明:即使去掉节点 U 也不影响图的连通性,U不是割点
-
如果节点U至少存在一个孩子节点,必须通过父节点 U 才能访问到 U 的祖先节点,那么去掉节点U后,顶点U的祖先节点和孩子节点就不连通了,此时 U 是一个割点
维护数组
-
dfn[] :依然表示顶点 u 被首次搜索到的序号
-
low[u] :表示顶点 u 及其子树中的点,通过非父子边(回边),能够绕到的最早的点(dfn最小)的 dfn 值(但不能通过连接 u 与其父节点的边)。这里不再是最早的祖先了,因为是无向图,所以并没有意义
-
cut[u]:表示 u 是否为割点
代码(链式前向星存图):
struct edge {
int next;
int to;
} E[2 * maxn];
void add(int x, int y) {
E[cnt].to = y;
E[cnt].next = head[x];
head[x] = cnt++;
}
void tarjan(int u, int from) {
DFN[u] = LOW[u] = ++idx;
int child = 0;
for (int i = head[u]; i != 0; i = E[i].next) {
int nx = E[i].to;
if (!DFN[nx]) {
tarjan(nx, from);
LOW[u] = min(LOW[u], LOW[nx]);
//如果不经过u无法到达u的祖先,那么一定是割点
if (LOW[nx] >= DFN[u] && u != from) cut[u] = true;
if (u == from) child++;
}
LOW[u] = min(LOW[u], DFN[nx]);
}
if (child >= 2 && u == from) cut[u] = true;
}
// 主函数中:
for (int i = 0; i < n; i++)
if (DFN[i] == 0) tarjan(i, i);
例题:PTA L2-013 红色警报 25分(邻接矩阵存图)
#include <bits/stdc++.h>
using namespace std;
typedef unsigned long long ull;
const int maxm = 5005;
inline int read() {
int x = 0, k = 1; char ch = getchar();
for (; !isdigit(ch); ch = getchar()) if (ch == '-') k = -1;
for (; isdigit(ch); ch = getchar()) x = x * 10 + ch - '0';
return x * k;
}
int n, m, k, cnt;
int low[maxm], dfn[maxm], idx;
bool cut[maxm];
int mp[maxm][maxm];
inline void tarjan(int u, int from) {
dfn[u] = low[u] = ++idx;
int child = 0;
for (int v = 0; v < n; v++) {
if (mp[u][v]) {
if (!dfn[v]) {
tarjan(v, from);
low[u] = min(low[u], low[v]);
if (low[v] >= dfn[u] && u != from) cut[u] = true;
if (u == from) child++;
}
low[u] = min(low[u], dfn[v]);
}
}
if (child >= 2 && u == from) cut[u] = true;
}
inline void init() {
idx = 0;
memset(dfn, 0, sizeof(dfn));
memset(low, 0, sizeof(low));
memset(cut, false, sizeof(cut));
for (int i = 0; i < n; i++)
if (dfn[i] == 0) tarjan(i, i);
}
int main() {
n = read(); m = read();
for (int i = 1; i <= m ;i++) {
register int u, v;
u = read(); v = read();
mp[u][v] = 1; mp[v][u] = 1;
}
k = read();
for (int i = 1; i <= k; i++) {
register int x; x = read();
init();
if (cut[x] == false) printf("City %d is lost.\n", x);
else printf("Red Alert: City %d is lost!\n", x);
for (int j = 0; j < n; j++) {
mp[j][x] = 0;
mp[x][j] = 0;
}
}
if (k == n) printf("Game Over.");
return 0;
}
求桥
桥的求法其实也是类似的,它的求法可以看成是割点的一种特殊情况,当结点 u 的子结点 v 的后代通过反向边只能连回 v,那么删除这条边(u, v)就可以使得图G非连通了。用 Tarjan 算法里面的时间戳表示这个条件,就是low[v] > dfn[u]。
int n, stamp, dfn[1005], low[1005];
int cnt, ansx[10005], ansy[10005];
vector<int> vec[1005];
int rank[1005];
void addAns(int x, int y) {
if (x > y) swap(x, y);
ansx[cnt] = x, ansy[cnt] = y;
cnt++;
}
void tarjan(int index, int from) {
int tmp;
dfn[index] = low[index] = ++stamp;
for (int i = 0; i < vec[index].size(); i++) {
tmp = vec[index][i];
if (!dfn[tmp]) {
tarjan(tmp, index);
low[index] = min(low[index], low[tmp]);
if (low[tmp] > dfn[index]) addAns(index, tmp);
}
else if (dfn[tmp] < dfn[index] && tmp != from) {
low[index] = min(low[index], dfn[tmp]);
}
}
}
例题等遇到了再补充orz