hoj 1098 NetWork标准求割点的做法(tarjan算法)

hoj 1098 NetWork //poj 1144 NetWork 求割点

标准求割点的做法(tarjan算法)

定理:

   设v是Gpi(是G的一棵深度优先搜索树)中的某一非根顶点,v是G的割点当且仅当

   v有一个子顶点s,使得不存在从s或s的任何后裔顶点指向v的某个真祖先顶点的反向边。

   如图:

       1------2

         \     /

           \ /

           3

          / \

        /    \

      4------5

   3为割点,因为以1为根的优先搜索树中,不存在从3或3的任何后裔顶点(即4和5)指向3的

   某个真祖先,同时因为3不是搜索树中的根节点,所以3为割点。

   tarjan算法使用的是dfs来构造一棵深度优先搜索树的,它从顶点1开始,用dfn[v]记录的是

   到达顶点v所用的时间(时间戳),而low[v]记录的是顶点v可以直接到达(即不经过任何的

   其它路径)的最早的祖先,我们用数据来描述一下tarjan算法是如何实现的吧:

 

   0、用数组map处理各顶点之间的关系:(0表示没直接通路,1表示有直接通路,2表示已经被访问过)

      map[1][2] = map[2][1] = map[1][3] = map[3][1] = map[2][3] = map[3][2] = map[3][4]

      = map[4][3] = map[4][5] = map[5][4] = map[3][5] = map[5][3] = 1

 

   1、从顶点1出发,记录dfn[1] = low[1] = 1,然后从顶点1开始判断是否有直接通路,所以先搜到

     的是顶点2,map[1][2] = map[2][1] = 2,由于顶点2还没被访问过(dfn[2]==0),从2出发dfs

     找到以它为跟的子树

  

   2、dfn[2] = low[2] = 2,由于顶点1与2已经被访问过,搜到3有直接通路,map[2][3] = map[3][2] = 2,

      因为3还没有被访问,dfs访问3

 

   3、dfn[3] = low[3] = 3,由1开始搜,因为map[1][3] = 1,表示该边还没被访问过,map[1][3] =

      map[3][1] = 2,由于1已经被访问过,所以顶点3所能到达的最早的真祖先的时间戳为1,即语句:

      low[v] = min(low[v],dfn[i]);的含义。又开始往下搜索,2时map[3][2] = 2已被访问过,所以搜到

      了顶点4,dfs访问4

 

   4、dfn[4] = low[4] = 4,由1开始到2没有直接通路,3时map[4][3] = 2已经被访问过,搜到了5,因5还没

      被访问,dfs访问5

 

   5、dfn[5] = low[5] = 5,从1到2没有直接通路,搜到3时,由于顶点3已经被访问过(dfn[3]!=0),所以

      更新一下顶点5所能到达的最早祖先的时间戳,所以low[5] = min(dfn[3],low[5]) = 3,由于没有顶点

      可被访问,所以这次dfs算是结束了,回溯到上一个顶点4

 

   6、回溯到的是语句low[v] = min(low[v],low[i]);,即low[4] = min(low[4],low[5]) = 3,所以顶点4同

      样可到达的最早的时间戳为3,low[4] = 3,由于low[5] = 3 < dfn[4] = 4,所以顶点4不是割点。回溯

      顶点3

 

   7、回溯到的是语句low[v] = min(low[v],low[i]);,即low[3] = min(low[4],low[3]) = 3,由于3不是根

      结点,而他的所有后裔能回到的最早时间戳为3,即low[3]>=dfn[3],所以3即为割点ans[3] = 1,回溯到2

 

   8、不详细讲(low[2] = 1,low[2]<dfn[2]),回溯到1

 

   9、由于low[1] = dfn[1],所以顶点1会被误认为是割点(至少这个例子是),所以给ans[1] = 1,所以在dfs

      完全结束后需要给ans[1]--,以免算多一个(假设1不是的话)。由此,tarjan算法结束

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
#define X 102
int map[X][X],dfn[X],low[X],ans[X],n,m,depth;
void dfs(int v)
{
   dfn[v] = low[v] = ++depth;
   for(int i=1;i<=n;i++)
      if(map[v][i]==1)
      {
         //cout<<"访问"<<v<<"  被访问"<<i<<endl;
         map[v][i] = map[i][v] = 2;
         if(!dfn[i])    //顶点没有被访问过
         {
            //cout<<"dfs"<<i<<endl;
            dfs(i);     //访问
            low[v] = min(low[v],low[i]);
            //printf("low[%d] = %d\n",v,low[v]);
            if(low[i]>=dfn[v])//因为顶点i及其后裔不存在指向顶点v的真祖先的反向边
                ans[v]++;
         }
         else        //该顶点已被访问过,更新该搜索的顶点的最早祖先
         {
            low[v] = min(low[v],dfn[i]);
            //printf("low[%d] = %d\n",v,low[v]);
         }
      }
}
void init()
{
   memset(map,0,sizeof(map));
   memset(dfn,0,sizeof(dfn));
   memset(low,0,sizeof(low));
   memset(ans,0,sizeof(ans));
   depth = 0;
}
int main()
{
   freopen("sum.in","r",stdin);
   freopen("sum.out","w",stdout);
   int x,y;
   while(cin>>n,n)
   {
      init();
      while(cin>>x,x)
      {
         while(getchar()!='\n')
         {
            cin>>y;
            map[x][y] = map[y][x] = 1;//表示连通
         }
      }
      dfs(1);
      ans[1]--;
      int cnt = 0;
      for(int i=1;i<=n;i++)
         if(ans[i]>0)
         {
            //cout<<"割点是:"<<i<<endl;
            cnt++;
         }
      cout<<cnt<<endl;
   }
 
   return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值