tarjan算法入门(二)——无向图的双连通分量(未完成)

版权声明:转载请注明原出处啦QAQ(虽然应该也没人转载): https://blog.csdn.net/hzk_cpp/article/details/82937383

一.双连通分量.

无向图的双连通分量分为两种,一种是点双连通分量,简称v-DCC;一种是边双连通分量,简称e-DCC.

一个v-DCC的概念即为一张没有割点的图,一个e-DCC的概念即为一张没有割边的图.

一张极大双连通分量子图的是指没有一张双连通分量子图完全包含这张子图的所有点且点数大于这张子图.

二.双连通分量的一些性质.

首先,我们可以发现,一张点数小于等于2的图一定是一个v-DCC.

若一个v-DCC的点数大于2,显然,v-DCC内的任意两点之间都有两条没有点相交的简单路径.

而很明显任意一个e-DCC,任意一条边都在一个简单环内.

三.e-DCC的求法.

定理1:去掉所有割边后,任意一个连通块都是一个e-DCC.

显然定理1成立.

那么我们只需要做一遍tarjan算法求出所有割边,将割边打上标记后再进行一遍dfs,就可以求出所有的e-DCC.

代码如下:

void tarjan(int k,int fa){
  dfn[k]=low[k]=++num;
  for (int i=lin[k];i;i=e[i].next){
    int y=e[i].y;
    if (!dfn[y]){
      tarjan(y,i);
      low[k]=min(low[k],low[y]);
      if (low[y]>dfn[k]) e[i].br=e[i^1].br=1;
    }else if (i^fa^1) low[k]=min(low[k],dfn[y]);
  }
}
void dfs(int k){
  edcc[k]=dcc;
  for (int i=lin[k];i;i=e[i].next)
    if (!edcc[e[i].y]&&!e[i].br) dfs(e[i].y);
}
void contract(){
  for (int i=1;i<=n;i++)
    if (!dfn[i]) tarjan(i,0);
  for (int i=1;i<=n;i++)
    if (!edcc[i]) ++dcc,dfs(i);
}

四.e-DCC的缩点.

将一张无向图每一个e-DCC都缩成一个点之后,显然我们会得到一个森林(如果原图连通则是一棵树).

那么缩点其实就是跑一遍e-DCC之后将得到的关系存到另一个邻接表中.

至于怎么构建这棵树,我们可以枚举所有的边,当这条边是一条割边的时候,就将它的顶点所对应的树点连接起来

代码如下:

void insc(int x,int y){
  ec[++tc].y=y;
  ec[tc].next=linc[x];
  linc[x]=tc;
}
void tarjan(int k,int fa){
  dfn[k]=low[k]=++num;
  for (int i=lin[k];i;i=e[i].next){
    int y=e[i].y;
    if (!dfn[y]){
      tarjan(y,i);
      low[k]=min(low[k],low[y]);
      if (low[y]>dfn[k]) e[i].br=e[i^1].br=1;
    }else if (i^fa^1) low[k]=min(low[k],dfn[y]);
  }
}
void dfs(int k){
  edcc[k]=dcc;
  for (int i=lin[k];i;i=e[i].next)
    if (!edcc[e[i].y]&&!e[i].br) dfs(e[i].y);
}
void contract(){
  for (int i=1;i<=n;i++)
    if (!dfn[i]) tarjan(i,0);
  for (int i=1;i<=n;i++)
    if (!edcc[i]) ++dcc,dfs(i);
  for (int i=2;i<=t;i++){
    int x=e[i^1].y,y=e[i].y;
    if (edcc[x]==edcc[y]) continue;
    insc(edcc[x],edcc[y]);
  }
}

猜你喜欢

转载自blog.csdn.net/hzk_cpp/article/details/82937383