tarjan算法入门(一)——割点割边

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

一.概述.

tarjan算法是Robert tarjan发明的一种基于深度优先遍历的算法,这种算法可以求无向图的割点(割顶)割边(桥),进一步可以用于求无向图的双连通分量;在有向图方面可以求出强连通分量等.

二.割点与割边.

割点与割边是图论中重要的概念.

割点的定义:在一张无向图中,去掉一个点,可以将这个点所在的联通块分成两个或多个联通块,则称这个点是这张无向图的割点.

割边的定义:在一张无向图中,去掉一条边,可以将这条边所在的联通块分成两个或多个联通块,则称这个点是这张无向图的割边.

三.tarjan算法与割边.

关于tarjan算法的定义,本人也不是很清楚,可能就是一类用到了生成树或者dfn与low值的深度优先遍历算法吧.

运用tarjan算法求割点与割边首先要了解dfn与low值.

dfn值:dfn值其实就是时间戳,深度优先遍历时一个点i的dfn值就是一个点被遍历到的顺序(即被遍历到之前有几个节点被遍历过).

low值:

我们dfs遍历出来的是一棵生成树,在这棵生成树上的边我们称作树边,其它边我们称作非树边.

那么一个点的low值即为一个点通过由非树边组成的路径能够达到的dfn值最小的点的dfn值.

定理1:对于任意一条边,这条边连接的两点在生成树上一定是祖先与后代的关系.

证明如下:

若一条边连接的两点在生成树上不是祖先与后代,那么dfs是应该会从先dfs到的那个点dfs到另一个点,与假设相矛盾.

证毕.

那么我们现在就可以搬出定理2了.

定理2:若一条边是割边,则这条边只会出现在生成树上,且这条边连接的父亲的dfn值要大于儿子的low值.

证明如下:

若这条边是非树边,则去掉这条边之后,生成树一节连通,与割边的定义相矛盾.

若这条边的儿子的low值大于或等于了父亲的dfn值,根据low值的定义,去掉这条边后,这个儿子能够连通到的dfn值最小的点要小于它的父亲.

根据定理1,我们可以知道一条边连接的点的关系一定是祖先与后代,所以这个儿子可以连通到祖先,联通块没有被破坏,与割边的定义相矛盾.

证毕.

那么现在我们要做的就是在一个dfs内求出dfn值和low值,就可以得到割边了.

那么算法流程如下:

1.进行dfs,先确定这个点的dfn值.

2.遍历这个点的所有儿子.

3.每遍历一个儿子后,利用定理2判定这个儿子到它的边是否为割边,并打上标记.

4.若一个儿子没有被遍历过,则将这个点当前的low值与它儿子的low值取min.

5.若这个点被遍历过,则将这个点当前的low值与它儿子的dfn值取min.

那么求割边的模板如下:

void dfs(int k,int fa){
  dfn[k]=low[k]=++tt;
  for (int i=lin[k];i;i=e[i].next){
    int y=e[i].y;
    if (!dfn[y]) {
      dfs(y,i);
      low[k]=min(low[y],low[k]);
      if (low[y]>dfn[k]) e[i].br=e[i^1].br=1;
    }else if (i^fa^1){      //不能变成判y是否k的父亲,否则会被重边卡
      low[k]=min(dfn[y],low[k]);
    }
  }
}

四.tarjan算法与割点.

接下来我们讨论割点.

定理3:一个割点若不是生成树的根,则这个点一定有一个儿子的low值小于等于这个点的dfn值.

证明如下:

设一个点为k.

若这个点k没有一个儿子满足这个条件,则所有儿子必定能不通过与点k之间的边通向k的父亲,这张图依旧联通,与割点的定义相矛盾.

证毕.

定理4:为根的节点为割点必须有两个儿子.

证明如下:

根据定理1,根节点的两棵子树一定不连通.

那么容易得出,若去掉根节点,根的所有子树都会分裂成单独的连通块.所以只要根有两个或两个以上的子树,就是割点.

证毕.

那么根据以上两条定理,我们就可以得出割点的算法流程:

1.进行dfs,将这个点k的dfn值确定,将low值初始为自己的dfn值.

2.遍历一遍它的所有儿子,同时更新low值.

3.判断这个点k是否是割点.

代码如下:

void dfs(int k){
  dfn[k]=low[k]=++num;
  int son=0;
  for (int i=lin[k];i;i=e[i].next){
    int y=e[i].y;
    if (!dfn[y]){
      dfs(y);
      low[k]=min(low[k],low[y]);
      if (low[y]>=dfn[k]){
        son++;
        if (k^root||son>1) cut[k]=1;
      }
    }else low[k]=min(low[k],dfn[y]);
  }
}

五.一些细节.

自环可以不用处理,但是输入时还是建议把自环跳过.

求割点的时候可以不用判断是不是父亲,但求割边必须判断.

若图不连通还需要将所有点跑一遍.

猜你喜欢

转载自blog.csdn.net/hzk_cpp/article/details/82915941
今日推荐