连通图小结 A Summary for Connected Graph
Ⅰ.概念
- 强连通
- 强连通:有向图中
(u,v) 存在u→v, v→u 的两条路径,称(u,v) 为强连通 - 强连通图:有向图中任意两个顶点强连通
- 强连通分量:有向图的极大强连通子图
- 强连通:有向图中
- 弱连通
- 弱连通:无向图中
(u,v) 存在u−v 的路径,称(u,v) 为弱连通 - 弱连通图:无向图中任意两个顶点弱连通,(可以将忽略方向的有向图看作无向图)
- [弱]连通分量:无向图的极大[弱]连通子图
- 弱连通:无向图中
- 割点与桥
- 割点:无向图中点
u ,删去后连通分量数目增加(连通图则使得图不再连通),称u 为割点 - 桥:无向图中边
(u,v) ,删去后连通分量数目增加(连通图则使得图不再连通),称(u,v) 为桥(割边)
- 割点:无向图中点
- 点双连通
- 点双连通:无向图中
(u,v) 存在至少两条“点不重复”的路径,称(u,v) 为点双连通
这个要求等价于任意两条边都在同一个简单环中,即内部无割点 - 点双连通分量:无向图的极大点双连通子图。
- 性质:
- 无向图每条边恰好属于一个点双连通分量
- 不同的点双连通分量之间可能会有公共点,最多只有一个且一定是割点
- 任意割点都是至少两个不同点双连通分量的公共点
- 点双连通:无向图中
- 边双连通
- 边双连通:无向图中
(u,v) 存在至少两条“边不重复”的路径,称(u,v) 为边双连通
这个要求低一点,只需要每条边都至少在一个简单环中,即所有的边都不是桥 - 边双连通分量:无向图的极大边双连通子图。
- 性质:
- 除桥不属于任何边双连通分量外,其他每条边恰好属于一个边双连通分量
- 把所有桥删除后,每个连通分量对应原图的一个边双连通分量
- 边双连通:无向图中
Ⅱ.连通图算法
- 连通分量
Connected Component
求解连通分量只需要dfs 或者bfs 把图搜一遍就好了,当然并查集也是可以的。
一般不爆栈的情况,dfs 好写适用性强
//Connected Components
vector<int> G[N];
int id[N], cc;
void dfs(int u) {
id[u] = cc;
for(int v : G[u]) {
if(id[v]) continue;
dfs(v);
}
}
void findCC() {
cc = 0;
for(int i = 1; i <= n; ++i) {
if(id[i]) continue;
++cc;
dfs(i);
}
}
- 割点
Cut
基于dfs 时间戳的Tarjan 算法
dfn[u]:= u 的时间戳,low[u]:= u 及其后代能连回的最早的祖先的时间戳
如果low[v]≥dfn[u] ,即v 及其所在的子树都没有后向边(back edge) 连回u的祖先
如果删去u ,那么u 的祖先与v 及其所在的子树不连通,根据“连通”关系的传递性,整个图就不连通了
//Tarjan-cut
vector<int> G[N];
int dfn[N], low[N], cut[N], dfsNum;
void tarjan(int u, int f) {
dfn[u] = low[u] = ++dfsNum;
int son = 0;
for(int v : G[u]) {
if(v == f) continue;
if(!dfn[v]) {
++son;
tarjan(v, u);
low[u] = min(low[u], low[v]);
if(low[v] >= dfn[u]) cut[u] = true;
} else low[u] = min(low[u], dfn[v]);
}
if(f < 0 && son == 1) cut[u] = false;
}
void init() {
dfsNum = 0;
memset(dfn, 0, sizeof dfn);
memset(cut, false, sizeof cut);
tarjan(root, -1);
}
- 桥
Bridge
基于dfs 时间戳的Tarjan 算法
dfn[u]:= u 的时间戳,low[u]:= u 及其后代能连回的最早的祖先的时间戳
如果low[v]>dfn[u] ,即v 及其所在的子树只能连回v 自己
如果删去(u,v) ,那么u 与v 及其所在的子树不连通,根据“连通”关系的传递性,整个图就不连通了
//Tarjan-bridge
vector<int> G[N];
vector<pair<int, int> > bridge;
int dfn[N], low[N], dfsNum;
void tarjan(int u, int f) {
dfn[u] = low[u] = ++dfsNum;
for(int v : G[u]) {
if(v == f) continue;
if(!dfn[v]) {
tarjan(v, u);
low[u] = min(low[u], low[v]);
if(low[v] > dfn[u]) bridge.push_back({u, v});
} else low[u] = min(low[u], dfn[v]);
}
}
void init() {
dfsNum = 0;
bridge.clear();
memset(dfn, 0, sizeof dfn);
tarjan(root, -1);
}
- 点双连通分量
Biconnected Component or Block
基于dfs 时间戳的Tarjan 算法
dfn[u]:= u 的时间戳,low[u]:= u 及其后代能连回的最早的祖先的时间戳
vector<int> G[N];
int dfn[N], low[N], cut[N], bcc, dfsNum;
vector<int> block[N];
int stk[N], top;
void tarjan(int u, int f) {
dfn[u] = low[u] = ++dfsNum;
stk[++top] = u;
int son = 0;
for(int v : G[u]) {
if(v == f) continue;
if(!dfn[v]) {
++son;
tarjan(v, u);
low[u] = min(low[u], low[v]);
if(low[v] >= dfn[u]) {
cut[u] = true;
block[++bcc].push_back(u);
while(true) {
int x = stk[top--];
block[bcc].push_back(x);
if(x == v) break;
}
}
} else low[u] = min(low[u], dfn[v]);
}
if(f < 0 && son == 1) cut[u] = false;
}
void init() {
bcc = dfsNum = 0;
memset(dfn, 0, sizeof dfn);
memset(cut, 0, sizeof cut);
tarjan(root, -1);
}
- 边双连通分量
Edge−biconnected Component
基于dfs 时间戳的Tarjan 算法
dfn[u]:= u 的时间戳,low[u]:= u 及其后代能连回的最早的祖先的时间戳
边双连通缩点其实就是留下桥,把所有桥边构成了新图,新图是一棵树
//Tarjan-bcc
vector<int> G[N];
int dfn[N], low[N], in[N], id[N], bcc, dfsNum;
int stk[N], top;
void tarjan(int u, int f) {
dfn[u] = low[u] = ++dfsNum;
stk[++top] = u;
in[u] = true;
for(int v : G[u]) {
if(v == f) continue;
if(!dfn[v]) {
tarjan(v, u);
low[u] = min(low[u], low[v]);
} else low[u] = min(low[u], dfn[v]);
}
if(low[u] == dfn[u]) {
++bcc;
while(true) {
int v = stk[top--];
in[v] = false;
id[v] = bcc;
if(v == u) break;
}
}
}
void init() {
bcc = dfsNum = 0;
memset(dfn, 0, sizeof dfn);
tarjan(root, -1);
}