基本概念
定义1:
割点集合:点集 V′∈V ,若 G 删除了
V′ 后不连通,但删除了 V′ 的任意真子集后 G 仍然连通,则称V′ 为割点集合割点:若某一结点就构成了割点集合,那么称此结点为割点或关节点。
点连通度:点数最少的割点集合
割边集合:边集 E′∈E ,若 G 删除了
E′ 后不连通,但删除了 E′ 的任意真子集后 G 仍然连通,则称E′ 为割边集合割边:若某一结点就构成了割边集合,那么称此边为割边或桥或关节边。
边连通度:边数最少的割边集合
块:没有割点的极大连通子图
定义2:
如果一个无向连通图的点连通度大于1,则称该图是点双连通的,简称双连通。通俗来说就是不存在割点。
如果一个无向连通图的边连通度大于1,则称该图是边双连通的,也简称双连通。通俗来说就是不存在割边。
可以看出,点双连通与边双连通都可以简称为双连通,它们之间是有着某种联系的。(双连通图一定既是点双连通的又是边双连通的)
定义3:
在图 G 的所有子图
G′ 中,如果 G′ 是双连通的,则称 G′ 为双连通子图。如果一个双连通子图 G′ 它不是任何一个双连通子图的真子集,则 G′ 为极大双连通子图。双连通分量,就是图的极大双连通子图。特殊的,点双连通分支又叫做块。
tarjan算法求割点和桥
tarjan算法求解双连通分量的做法和求解有向图的强连通分量做法类似。
点双连通分量
求点双连通分量也就是去找割点
在dfs搜索树中,如果点k满足下列两个条件之一,那么k是割点:
- k 为深搜树的根,且 k 的儿子个数 ≥2
- k 为深搜树的中间节点(k 既不是根也不是叶),且 low[son]>=dfn[k]
第一个条件很好理解,第二个条件son表示k的儿子,这句话的意思也就是说k的子孙中不会有某个点有追溯到k的祖先的边。
这样,对于求点连通分量算法中low值的严格定义将更新为:
low(u)=Min
{
dfn(u)
low[v] v是u 的一个儿子
dfn[v] v与u邻接,且边(u,v)是一条返祖边
}算法具体细节见代码:
void tarjan(int p,int u) { dfn[u] = low[u] = ++ti; // 时间戳 int son = 0; // 当前节点儿子的个数 for (k=head[u]; k!=-1; k=edge[k].next) //前向星实现 { int v = edge[k].to; if (v == p) continue; if (dfn[v] == 0) { son++; tarjan(u,v); if (low[v] < low[u]) low[u] = low[v]; if((u!=1 && dfn[u]<=low[v]) || (u==1&&son>=2)) istcc[u]=1; // 条件符合,u是割点 } else low[u] = min(low[u],dfn[v]); } }
边双连通分量
求边双连通分量也就是去找割边
割边的求解过程和求割点的方法类似,判断方法是:
无向图中的一条边(u,v)是割边,当且仅当(u,v)是生成树中的边,且满足 dfn[u]<low[v] 。
找到一条割边就将割边下面的边连通分量出栈。
stack<int> St; void Tarjan(int p,int u) { dfn[u]=low[u]=++ti; St.push(u); vis[u]=1; for(int k=head[u];k!=-1;k=edge[k].next) { int v=edge[k].v; if(v==p)continue; if(!dfn[v]) { Tarjan(u,v); low[u]=min(low[u],low[v]); if(low[v] > dfn[u]){ cnt_bri++;//双联通分量块的个数,多一个桥即多一个双联通分量 } else// if(vis[v]==1)//v在栈中,说明(u,v)是返祖边 { low[u]=min(low[u],dfn[v]); } } if(dfn[u]==low[u])//将u所在的边连通分量出栈 { int x; ++cnt;//cnt表示缩点后的连通分量 while(1) { x=St.top(); St.pop(); vis[x]=0; belong[x]=cnt; if(x==u)break; } } }