1.求割点:
割点:若删掉某点后,原连通图分裂为多个子图,则称该点为割点。
原理:若low[v]>=dfn[u],则u为割点。因low[v]>=dfn[u],则说明v通过子孙无法到达u的祖先。那么对于原图,去掉u后,必然会分成两个子图。
桥(割边):删掉它之后,图必然会分裂为两个或两个以上的子图。
原理:若low[v]>dfn[u],则(u,v)为桥。
(无向图)代码:
(无向图)综合代码:
割点:若删掉某点后,原连通图分裂为多个子图,则称该点为割点。
原理:若low[v]>=dfn[u],则u为割点。因low[v]>=dfn[u],则说明v通过子孙无法到达u的祖先。那么对于原图,去掉u后,必然会分成两个子图。
所以处理节点u时,先递归v的子节点,然后回溯至u时,如果满足low[v]>=dfn[u],则u为割点。
代码:
int dfn[maxn], low[maxn];
int gcnt, iscut[maxn];
struct Edge{
int v, next;
}E[maxn * maxn];
int head[maxn], tol;
void Init(){
memset(head, -1, sizeof head);
memset(dfn, 0, sizeof dfn);
memset(iscut, 0, sizeof iscut);
gcnt = tol = 0;
}
void add_edge(int u, int v){
E[tol].v = v;
E[tol].next = head[u];
head[u] = tol++;
}
void Tarjan(int u, int pre){
dfn[u] = low[u] = ++gcnt;
int son = 0;
for(int i = head[u]; ~i; i = E[i].next){
int v = E[i].v;
if(v == pre) continue;
if(!dfn[v]){
son++;
Tarjan(v, u);
low[u] = min(low[u], low[v]);
if(u != pre && low[v] >= dfn[u]) iscut[u] = 1;
}else low[u] = min(low[u], dfn[v]);
}
if(u == pre && son > 1) iscut[u] = 1;
}
2.求桥
桥(割边):删掉它之后,图必然会分裂为两个或两个以上的子图。
原理:若low[v]>dfn[u],则(u,v)为桥。
(无向图)代码:
int dfn[maxn], low[maxn];
int gcnt, iscut[maxm];
struct Edge{
int v, next;
}E[maxm];
int head[maxn], tol;
void Init(){
memset(head, -1, sizeof head);
memset(dfn, 0, sizeof dfn);
memset(iscut, 0, sizeof iscut);
gcnt = tol = 0;
}
void add_edge(int u, int v){
E[tol].v = v;
E[tol].next = head[u];
head[u] = tol++;
}
void Tarjan(int u, int pre){
dfn[u] = low[u] = ++gcnt;
for(int i = head[u]; ~i; i = E[i].next){
int v = E[i].v;
if(i == (pre ^ 1)) continue;//因为无向图简图用双向边,此处是消除双向边的影响
if(!dfn[v]){
Tarjan(v, i);
low[u] = min(low[u], low[v]);
if(low[v] > dfn[u]) iscut[i] = iscut[i ^ 1] = 1;
}else low[u] = min(low[u], dfn[v]);
}
}
(无向图)综合代码:
int Vcnt=0;
int Ecnt=0;
dfs(int u,int father,int depth) //u是当前结点,father父节点,depth记录dfs标号
{
visit[u]=1;
low[u]=dfn[u]=depth;
int child=0;//记录u的孩子数
for (int i=0;i<G[u].size();i++)
{
int v=G[u][i];
if (visit[v]==1&&v!=father)
low[u]=min(low[u],dep[v]);
if (visit[v]==0)
{
child++;
dfs(v,u,depth+1);
low[u] = min(low[u], low[v]);
if(u==root&&child>1||u!=root&&low[v]>=dfn[u])
{
cutpoint[u]=1;//是割点
Vcnt++;
}
if (low[v] > dfn[u])
{
//<u,v>是割边;
Ecnt++;
}
}
}
visit[u]=2;
}
3.点双连通分量 边双连通分量
点双连通图的定义等价于任意两条边都同在一个简单环中,而边双连通图的定义等价于任意一条边至少在一个简单环中。对一个无向图,点双连通的极大子图称为点双连通分量(简称双连通分量),边双连通的极大子图称为边双连通分量。
4.有向图强连通分量 无向图连通分量
(有向图)代码:
int dfn[maxn], low[maxn];
int sta[maxn], top, gcnt, sub;
int id[maxn], inS[maxn];
int ind[maxn], outd[maxn];
struct Edge{
int v, next;
}E[maxn * maxn];
int head[maxn], cnt;
void Init(){
memset(head, -1, sizeof head);
memset(dfn, 0, sizeof dfn);
memset(inS, 0, sizeof inS);
gcnt = cnt = top = scc = 0;
}
void add_edge(int u, int v){
E[cnt].v = v;
E[cnt].next = head[u];
head[u] = cnt++;
}
void Tarjan(int u){
dfn[u] = low[u] = ++gcnt;
sta[++top] = u;
inS[u] = 1;
for(int i = head[u]; ~i; i = E[i].next){
int v = E[i].v;
if(!dfn[v]){
Tarjan(v);
low[u] = min(low[u], low[v]);
}else if(inS[v])
low[u] = min(low[u], dfn[v]);
}
if(dfn[u] == low[u]){
scc++;
while(1)//记录每一个点属于的连通块
{
int v=sta[top--];
id[v] = scc;
inS[v] = 0;
if(v==u)
break;
}
}
}
(无向图)代码:
struct Edge{
int v, next;
}E[maxm << 1];
int head[maxn], tol;
int dfn[maxn], low[maxn], id[maxn], sta[maxn], inS[maxn],top, cnt, scc;
void init(){
memset(head, -1, sizeof head);
tol = cnt = top = sub = 0;
memset(dfn, 0, sizeof dfn);
memset(inS, 0, sizeof inS);
}
void add_edge(int u, int v){
E[tol].v = v;
E[tol].next = head[u];
head[u] = tol++;
}
void Tarjan(int u, int pre){
dfn[u] = low[u] = ++cnt;
sta[++top] = u;
inS[u] = 1;
for(int i = head[u]; ~i; i = E[i].next){
if(i == (pre ^ 1)) continue;
int v = E[i].v;
if(!dfn[v]){
Tarjan(v, i);
low[u] = min(low[u], low[v]);
}else if(inS[v]){
low[u] = min(low[u], dfn[v]);
}
}
if(dfn[u] == low[u]){
int v;
sub++;
do{
id[v = S[top--]] = scc;
inS[v] = 0;
}while(u != v);
}
}
for(int i = 1; i <= n; i++){
if(!dfn[i]) Tarjan(i, -1);
}