Tarjan算法求割,桥,块(点双连通分支),边双连通分支总结

2011/10/12--2011/10/17眨眼5天过去了,这几天基本上在忙着招新的事情,虽然我的并不是太对招新负责,但是招新海报是用的我的照片啊!!压力太大了,发现这次的新队员应该比我们这些老家伙更有能力,聪明多了,不知道他们肯不肯付出咯~

言归正传,开始总结:

Tarjan大神DFS的三个算法终于都学会了。

1.Tarjan求最近公共祖先。

2.Tarjan求强连通分量。

3.Tarjan求双连通分支。

这篇文章介绍第三项:Tarjan求双连通分支;

基本概念:

1.割点。在无向图中存在这样一个点,切除该点图的连通分量数+1.也就是说原有的一个连通分量经过操作成为两个连通分量。

2.桥。在无向图中存在这样一条边,删掉该边图的连通分量数+1.

3.块(点双连通分支)。在一个极大连通子图中,该连通分支的点连通度≥2,既至少删除两个点才能破坏无向图的连通性。

4.边双连通分支。在一个极大连通子图中,该连通分支的边连通度≥2。

求解方法:

由dfs构造的一棵搜索树中:

1.割点:点u为割点,满足以下两个条件之一

1>.u为树的root && u的孩子≥2个

2>.u不为树的root && 满足DFN[u]<=LOW[v];

其中DFN为dfs过程中节点的编号。LOW为该点引出的边中最多直接或间接连接的最早的祖先。

1>.条件很形象.2>.当出现DFN[u]<=LOW[v]时,也就是说v点最多达到u,不可能再向上达到u的祖先节点。显然这个时候将u点删除,v所在的一团和u点祖先的一团分割为两个连通分量。所以u为割点。

2.桥:边(u,v)为桥,满足该条件:DFN[u]<LOW[v]。这里照上面的讲解也很形象。当DFN[u]==LOW[v]时,当u->v dfs递归,存在一条v->u的回边,使得LOW[v]=DFN[u];故不为桥。

割和桥的两种判定就像上面的讲述:

void Tarjan( int u,int father )
{
     Node *p=ptr[u];
     DFN[u]=LOW[v]=++DEP;
     while( p )
     {
            if( DFN[p->v]==0 )
            {
                Tarjan( p->v,u );
                LOW[u]=min( LOW[u],LOW[v] );
                //if DFN[u]<LOW[v] 则(u,v)为桥; 
            }
            else if( p->v!=father )
                 LOW[u]=min( LOW[u],DFN[v] );
            p=p->next;
     }
     /*
     if( u is root )
         u是割点 <=> u至少有两个孩子
     else
         u是割点 <=> DFN[u]<=LOW[v] 
     */ 
}
下面具体的求桥和割点的方法:

首先用Tarjan标记DFN和LOW数组

void getV_B()
{
     Tarjan(1,0);
     int rootSon=0,cutPoint=0;
     for( i=2;i<=N;i++ )
     {
          if( father[i]==1 )
              rootSon++;
          else if( DFN[father[i]<=LOW[i]] )
              cutPoint++;//i为割点
     }
     if( rootSon>=2 )
         cntPoint++;//root点1为割点
     for( i=1;i<=N;i++ )
          if( DFN[father[i]]<LOW[i] )
              //( father[i],i )为桥; 
}
3.边双连通分支:

也就是将桥删除后整个图分成的连通块就是边双连通分支。

4.点双连通分支:

对于点双连通分支,实际上在求割点的过程中就能顺便把每个点双连通分支求出。建立一个栈,存储当前双连通分支,在搜索图时,每找到一条树枝边或反向边,就把这条边加入栈中。如果遇到某时满足dfn(u)<=low(v),说明u是一个割点,同时把边从栈顶一个个取出,直到遇到了边(u,v),取出的这些边与其关联的点,组成一个点双连通分支。割点可以属于多个点双连通分支,其余点和每条边只属于且属于一个点双连通分支。

void Tarjan(int u, int father){	
	int i,j,k;
	low[u] = dfn[u] = nTime ++;
	for( i = 0;i < G[u].size() ;i ++ ) 
    {
		int v = G[u][i];
		if( ! dfn[v]) 
        { //v没有访问过//树边要入栈
			Edges.push_back(Edge2(u,v));
			Tarjan(v,u);
			low[u] = min(low[u],low[v]);
			Edge2 tmp(0,0);
			if(dfn[u] <= low[v]) 
            { //从一条边往下走,走完后发现自己是割点,则栈中的边一定全是和自己在一个双连通分量里面//根节点总是和其下的某些点在同一个双连通分量里面
			      cout << "Block No: " << ++ nBlockNo << endl;
			      do 
                  {
					 tmp = Edges.back();
					 Edges.pop_back ();
					 cout << tmp.u << "," <<tmp.v << endl;
                   }while ( !(tmp.u == u && tmp.v == v) );
			}
		}  // 对应if( ! dfn[v]) {
		else if( v != father )
        {
             //u连到父节点的回边不考虑
             low[u] = min(low[u],dfn[v]);
             if( dfn[u] > dfn[v])//子孙连接到祖先的回边要入栈,但是子孙连接到自己的边,此处肯定已经入过栈了,不能再入栈
                 Edges.push_back(Edge2(u,v));
		}
	} //对应 	for( i = 0;i < G[u].size() ;i ++ ) {
}













  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值