图的割边、割点、块、缩点问题

来源:互联网 发布:印度软件为什么发达 编辑:程序博客网 时间:2024/04/30 08:19
 

首先要明确割点与割边的概念,

割点:在一个连通图中,如果去掉了某个点和所有与这个点相连的边后,是图分成了两个部分,变成了一个不连通的图。那么这个点就是割点。

割边:在一个连通图中,如果去掉了某个点,把图分成了两个部分,变成了一个不连通的图,那么这个边就是割边。

那么我们先来看看如何求割点,最简单的方法呢,其实就是,挨个点都试验一遍,去掉这个点以及与这个点相连的所有边之后,用dfs或bfs来判断这个图是否还是连通的,但这个算法的时间复杂度是N^3的,算法效率很低。所以我们再来想想其他办法的使用。

 

根据割点的定义,割点将图分成了两个部分,这个两个部分是靠割点来连通的,如果没有割点存在,那么图将不连通。所以,换句话说,我们要从第一部分的点进入第二部分的话,我们必须经过这个割点。然后接着,我们再考虑图的dfs的时候。由于割点是通往第二部分的门户,那么当我们从第一部分开始对图进行dfs的时候,如果访问了割点,那么再一次回退到割点的时候,必须是等第二部分的所有的点都访问完了之后才可以,否则就相当于“大门”被关闭了。第二部分的点就无法再次访问到了。因为dfs遇到访问过的点就立刻返回。

对dfs来说,她遍历的最后是生成一棵树,其中树有一些叶子节点,形成叶子节点的原因是,他们能接触到的点都已经在他们之前访问过了。所以到了他们之后dfs不再继续递归了。联系到上面,我们可以看出dfs遍历所形成的树中的图的第二部分的点,与他们相连的只能是图中第二部分的点,或者是割点。所以我们如果对dfs访问的点按顺序来进行标号的话,那么第二部分的点的被访问序号一定要比割点的访问序号要大。

所以我们就想到了利用这个特性,用dfs求割点的方法:

首先从任意点开始,进行dfs,

每递归到一个点,如果这点是没有被访问的,那么用dfs的序号给这个点的n1赋值,然后还要接着去访问与这个点相邻的其他的点,把其中最小的返回值返回,作为这个点的n2值。如果相连的点是被访问过的,那么直接把该店的n1作为返回值。

然后,在访问的过程中,还要记录下序边,下序边是指,在生成的树中,由父节点指子节点的边。是有方向的。

当dfs结束后。对于每一个点,从它的下序点中找到最小的n2的值,若果n1<=n2,那么就说明,他的下序点中,没有比它先被访问的点,那就说明这个点是一个割点。若果n1>n2,那么这个点不是割点。

(但要注意,这个算法有一个默认的前提假设,就是说,dfs的起始点是被割点分成的两个部分中的点,但是如果这个起始点开始就是割点。则最后的比较是找不到割点的,所以最后我们还需要一个单独对这个起始点的判断,用最简单的dfs或者bfs即可)。

而对于求割边得算法呢,做法是一样的,只是不用记录下序边,在最后形成的树中,如果AB是一条边,如果A的n1要比B的n1小的话,并且B的n2>A的n1,那么这个边就是一个割边。

以上就是求割边与求割点的做法,下面我们来看看一个例题:

一个电话公司,通过一个电话网络提供服务。这个网络有电话线和交换站组成,线路是双向的,每个居住点都会设置一个交换站点,并且这个网络是全部连通的,任意两个居住点都是连通的。但是常常会有个别站点出问题,而且,有些站点出问题后,除了本居住地无法与外界交换信号以外,其他的站点也要受影响。

 

这个例题的意思实际上就是求割点的意思。下面只是简要的写一段最主要的核心部分的代码:

下面就看一下这个简要的源代码:

 

int dfs(int ind)

{
      if(pre[ind])  return prep[ind];    //如果已经访问过,那么直接将n1作为返回值。

     int i,t,min;

            pre[ind] = ++cnt;                     //给n1赋值

      min = pre[ind];           //将初始值设置成本身的n1

      for(int i = 1;i<=n;i++)

      {

           if(map[i][ind]&&path[i][ind]!=1)    //表示i与ind之间有边相连,但i不是ind的直接

父节点。算法中不往父节点进行回退。

           path[ind][i] = 1;                               //将i设置成ind的直接孩子。

           t = dfs(i);                                          //从直接子节点处开始继续访问。

           if(t<min)     min = t;                    //更新最小值。

      }

      return after[ind] = min;

  

 

下面是在dfs过程进行结束之后,判断割点的过程。

void checkALL()

{

      int i,j,max;

      checkFirst();                      //因为初始点,是不是割点都符合n2>=n1的条件,所以要对初始的dfs的点再进行一次检查,检查采用一般的方法就可以。

      for(i =2 ;i<=n;i++)

      {

           max = -1;

           for(j=1;j<=n;j++)

           {

                 if(path[i][j]==1)

                 {

                      if(max<after[j]) max = after[j];    //为什么是最大值呢,因为某个点的下序边可能有很多,只要最大的那个能满足n2 >= n1的条件,就可以说明是这个点是割点           

                 }

           }

           if(max >= pre[i])     mark[i] = flag                  //证明i是割点

      }

  

原文地址:http://blog.sina.com.cn/s/blog_509a46a60100tjsn.html
原创粉丝点击