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); }