tarjan算法入门(三)——有向图的强连通分量

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

一.概述.

强连通分量SCC是基于有向图的一个概念,即“极大连通分量”.有向图的强连通分量就是说一张图G的子图G',G'的每一个点u都可以遍历到这张图上的任意一个点v,且这张子图G'极大,极大的意思可以参考双连通分量的极大.

二.强连通分量与tarjan算法.

tarjan算法可以在线性时间内找出一张图的所有强连通分量.

与无向图的tarjan算法类似,有向图tarjan算法也需要引入搜索树的概念,也有树边与非树边还有low值与dfn值的概念.这些概念在无向图部分已经讲解,这里不再赘述.

但是在有向图时,有可能会出现一条非树边连接的两个节点不是祖先与后代的关系.

当一个点x满足low[x]=dfn[x]时,我们发现这个点的子树中满足low[y]=dfn[x]的点y,所有点y就会与x组成一个强连通分量.

那么我们可以维护一个栈,当一个点x被遍历到时,将点x压入栈.遍历完整棵子树后,若dfn[x]=low[x],则我们点x到栈顶的所有点都标记为一个新的强连通分量,并将点x到栈顶的所有点出栈.

正如上面所说,会出现非树边连接的两个节点不是祖先与后代的关系,所以我们将low值定义为,若dfn[y]=low[x],则y必须在栈中.

tarjan算法求解有向图强连通分量代码如下:

扫描二维码关注公众号,回复: 3619008 查看本文章
void tarjan(int k){
  dfn[k]=low[k]=++num;
  st[++ts]=k;use[k]=1;
  for (int i=lin[k];i;i=e[i].next){
    int y=e[i].y;
    if (!dfn[y]){
      tarjan(y);
      low[k]=min(low[k],low[y]);
    }else if (use[y]) low[k]=min(low[k],low[y]);
  }
  if (dfn[k]^low[k]) return;
  cnt++;
  while (st[ts]^k){
    scc[st[ts]]=cnt;
    use[st[ts--]]=0;
  }
  scc[k]=cnt;use[st[ts--]]=0;
}
void contract(){
  for (int i=1;i<=n;i++)
    if (!dfn[i]) tarjan(i);
}

三.SCC缩点.

我们可以发现,当我们将每一个SCC都看成一个点之后,整张图就会变成一张有向无环图,所以我们有了SCC缩点.

SCC缩点就是新建一张图,将所有SCC都缩成一个点之后的一张DAG.

代码如下:

void tarjan(int k){
  dfn[k]=low[k]=++num;
  st[++ts]=k;use[k]=1;
  for (int i=lin[k];i;i=e[i].next){
    int y=e[i].y;
    if (!dfn[y]){
      tarjan(y);
      low[k]=min(low[k],low[y]);
    }else if (use[y]) low[k]=min(low[k],low[y]);
  }
  if (dfn[k]^low[k]) return;
  cnt++;
  while (st[ts]^k){
    scc[st[ts]]=cnt;
    use[st[ts--]]=0;
  }
  scc[k]=cnt;use[st[ts--]]=0;
}
void contract(){
  for (int i=1;i<=n;i++)
    if (!dfn[i]) tarjan(i);
  for (int i=1;i<=n;i++)
    for (int j=lin[i];j;j=e[j].next)
      if (scc[i]^scc[e[j].y])
        insc(scc[i],scc[e[j].y]);
}

猜你喜欢

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