《啊哈算法》——割点割边二分图

Posted 黑大帅之家

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了《啊哈算法》——割点割边二分图相关的知识,希望对你有一定的参考价值。

  这篇文章我们简单的介绍求解图的割点、割边和二分图相关的概念。

 

  割点:

  对于含n个点、m条边的连通无向图G,如果去掉顶点vi(并同时去掉与之相连的边),使得G不再连通,那么称vi是一个割点。

  通过其定义,我们不难判断某个点是否是割点,但是现在我们面临的问题是,如何给出一个图G,编码让计算机求解割点呢?

  首先我们考虑这样一个问题,判定某个点的指标是什么。我们通过人脑来判断其是否是割点,其实是利用非常模糊的视觉效应,即“通过去掉该点观察图是否连通”即可,而如果想要通过计算机来判断,就需要非常量化的判断条件。

  我们考虑从深度优先搜索的角度来找到这样一个判断条件,利用dfs遍历图,得到的生成子图本质上会得到一个生成树,我们拿出两个相邻的点vi、vj,vi是vj的父节点。我们回到深搜遍历的过程中,假设当前遍历到vj,如果我们从vj能够找到一条回到已经访问过的v1、v2...等节点,那么这表明去掉vi,将不会影响剩余图的连通性。

  我们似乎发现了些什么,但是这种判定关系还是有些模糊。

  我们借用这样一个概念——时间戳,即深度优先搜索的过程中,我们记录访问节点的顺序,我们用num[i]来表示节点vi的时间戳,即在深搜遍历过程中第几个访问vi节点。借用这个工具,我们考虑能不能将上述我们描述的关系用量化的表达式表示出来呢?好像还是有点捉襟见肘啊,我们不妨再设置一个数组low[i],用以表达vi不经过dfs的生成树的父节点所能够到达的时间戳最小的节点(好好理解,非常拗口),基于这个工具,我们能够看到上述的判断条件,可以用这样一个表达式简洁的概括:

                                                                          low[i] < num[i]

  那么现在我们首要的问题似乎变成了求解n个节点的low[]、num[]了。

  首先,对于num[],也就是时间戳的记录,并不困难。而对于low[]数组的求解,就需要动一些脑筋了。我们模拟遍历过程,当前遍历到vi点,我们访问所有与vi连通的点vj,会出现如下两种情况。

  1.vj访问过,被我们打上过时间戳,  那么我们此时需要更新low[i]了,即low[i] = min{num[j] | vj与vi连通}。

  2.vj没有访问过,那么我们继续深搜遍历点的过程。

  在遍历完成之后,也完成了num[]、low[]的求解,我们再利用深搜的回溯过程,完成判断即可。

  这里需要注意的一点是,对于某个图的根节点,即dfs开始的那个点(记作v1)其实是不满足上文给出的判断式子的,需要我们特殊判断,记child是根节点的子树个数,则v1是个割点的必要条件是,child  = 2。

  简单的参考代码如下。

 

#include<cstdio>
#include<algorithm>
using namespace std;

int n , m , e[9][9] , root;
int num[9] , low[9] , flag[9],index;
int min(int a , int b)
{
     return a < b ? a : b;
}
void dfs(int cur , int father)
{
    int child = 0 , i , j;

      index++;
      num[cur] = index;
      low[cur] = index;
      for(i = 1;i <= n;i++)
      {
            if(e[cur][i] == 1)
            {
                   if(num[i] == 0)  //第一种情况
                   {
                         child++;
                         dfs(i,cur);
                         low[cur] = min(low[cur] , low[i]);//回溯过程:判断割点

                         if(cur != root && low[i] >= num[cur])
                              flag[cur] = 1;
                         else if(cur == root && child == 2)
                              flag[cur] = 1;
                   }
                   
                    else if(i != father) //第二种情况
                    {
                        low[cur] = min(low[cur] , num[i]);
                    }

            }
      }
}

int main()
{
     int i , j, x , y;
     scanf("%d %d",&n,&m);
     for(i = 1;i <= n;i++)
          for(j = 1;j <= n;j++)
             e[i][j] = 0;
     for(i = 1;i <= m;i++)
     {
          scanf("%d %d",&x,&y);
          e[x][y] = 1;
          e[y][x] = 1;
     }

     root = 1;
     dfs(1,root);

     for(i = 1;i <= n;i++)
     {
           if(flag[i] == 1)
              printf("%d ",i);
     }

     return 0;
}

 

 

以上是关于《啊哈算法》——割点割边二分图的主要内容,如果未能解决你的问题,请参考以下文章

tarjan求割点割边的思考

tarjan求割点割边的思考

图灵算法群《啊哈算法》领读

poj1144 割点割边问题 dfs暴力

Tarjan 割点割边模板

图的割点与割边(超详细!!!)