对于有向图中,连通分量叫强连通分量
对于无向图中,连通分量叫双连通分量,而在双连通分量中,又分为点双连通和边双连通。
重点讨论双连通的情况:
以割点区分连通情况的双连通叫做点双连通分量,以割边区分连通情况的双连通叫做边双连通分量。
比如这个图中:
1 4
| \ / \
| \ / \
| 2 5
| / \ /
| / \ /
3 6
如果要的到两个连通分量的话,我们只有以2节点为割点,将图分为点双连通。我们便可以得到两个双连通分量(1,2,3)、(2,4,5,6)。
再比如这个图:
1 5
| \ / \
| \ / \
| 2 — 4 6
| / \ /
| / \ /
3 7
如果要得到两个连通分量的话,我们只能以(2,4)边为割边,得到两个边双连通分量。
具体是将一个无向图分成点双连通还是边双连通得看具体的问题。下面具体讨论一下用tarjan算法实现起来的区别。
1、对于边双连通分量,由于是以边为连通间的区分,那么每个点只属于一个连通分量,我们在深度遍历的时候,只需要记录某一点是否被访问即可。
2、但是对于点双连通分量,由上图也可以看出,2节点同时属于两个连通分量,如果还是以节点为重判对象的话,将得到错误的答案。此时,我们应该以边为重判对象,而该边的两个端点,都同属于一个点双连通。
对于边双连通的实现,由于没有节点会重复属于两个以上的连通分量,所以我们可以先简单的遍历一遍整个图的tarjan标号(low、dnf)。之后再进行一次深度遍历,以low[v]>dnf[u]为换色标准染色,最后每种颜色即一个分量。
代码:
void tarjan(int u,int f) //f为父节点 { low[u]=dnf[u]=++count; visit[u]=1; S.push(u); for(node *p=link[u];p;p=p->next) { if(p->v==f) continue; if(!visit[p->v]) //一节点为重判对象 { tarjan(p->v,u); if(low[u]>low[p->v]) { low[u]=low[p->v]; } } else if(low[u]>dnf[p->v]) low[u]=dnf[p->v]; } } void set_color(int u) { for(node *p=link[u];p;p=p->next) { if(!color[p->v]) { if(low[p->v]>dnf[u]) //找到了关键割边,换一种颜色染色 color[p->v]=++cut; else color[p->v]=color[u]; //保持原来的颜色 set_color(p->v); } } }
对于点双连通的实现,经过上面的分析,由于存在点既属于A分量又属于B分量,所有我们此时采纳边为重判对象,而该边的两端点同属于一个分量
代码:
void col(int u) { int x;node *p; ++cut; do { p=stack[--top]; color[p->v]=color[p->op->v]=cut; }while(p->op->v!=u); } void tarjan(int u) { low[u]=dnf[u]=++cut; for(node *p=link[u];p;p=p->next) { if(p->vist) //该边已被访问 continue; p->vist=p->op->vist=1; //两个方向都标记已访问 stack[top++]=p; //该边进栈 if(!dnf[p->v]) { tarjan(p->v); if(low[u]>low[p->v]) low[u]=low[p->v]; if(low[p->v]>=dnf[u]) //找到了关键割点 col(u); } else if(low[u]>dnf[p->v]) low[u]=dnf[p->v]; } }