无向图的割点,桥,双连通分量,有向图的强连通分量总结

                 一、无向图的割点,桥,双连通分量

1.割点:

 定义:

    在一张无向图中,如果去掉某个顶点以及和这个顶点相关联的边,使得整个图的连通分支数增  加,那么这个点就是一个割点.

 tarjan算法求无向图的割点:

   定义low[u],DFN[u]分别表示u可以到达的最早被访问到的祖先的时间,后者表示u被访问到的时间,那么u是割点当且仅当u满足下面2种情况之一:

    1).u是dfs搜索树的根,并且u含2棵及2棵以上的子树

    2).u不是dfs搜索树的根,并且有不等式 low[v]>=DFN[u],其中(u,v)是树枝边(即v通过u延伸开去)

 简单说明这两个情况:

    情况1:这是很显然的,如果这个时候u只有1棵子树,那么就算去掉u,整个图的连通分支数还是不会变,但是当子树个数超过1的时候,去掉u,连通分支数是会增加的.

    情况2:如果不等式low[v]>=DFN[u]成立,言外之意就是v要到达其祖先必须通过u,否则如果有其他边可以让v到达祖先的话,low[v]显然会比DFN[u]来的小,那么这时候去掉点u,图的连通分支数一定会增加.

 代码:

   

<span style="font-family:KaiTi_GB2312;font-size:18px;">void tarjan(int u,int root,int fa)  
{  
    DFN[u]=low[u]=++index;  
    instack[u]=1;  
    int cnt=0;  
    for(int i=head[u];i!=-1;i=edge[i].next)  
    {  
        int v=edge[i].to;  
        if(!instack[v])  
        {  
            tarjan(v,root,u);  
            cnt++;  
            if(low[u]>low[v])  
                low[u]=low[v];  
            if(u==root && cnt>1)  
                cut_point[u]=1;  
            else if(u!=root && low[v]>=DFN[u])  
                cut_point[u]=1;  
        }  
        else if(v!=fa && low[u] > DFN[v])  
            low[u]=DFN[v];  
    }  </span>


2.桥

    定义:

     在一张无向图中,如果去掉边(u,v)使得图的连通分支数增加,那么边(u,v)便称为桥.

  tarjan算法求无向图的桥:

     边(u,v)是无向图的桥当且仅当(u,v)满足 low[v]>DFN[u]

   简单说明:

     这个式子的言外之意也就是边(u,v)是v到达其祖先的必经之路,所以去掉边(u,v),连通分支数就会增加,但是如果u,v之间存在重边的话,就不是桥了,所以我们要对重边判定.

   代码:

    

void tarjan(int u,int fa)  
{  
    DFN[u]=low[u]=++index;  
    instack[u]=1;  
    for (int i=head[u];i!=-1;i=edge[i].next)  
    {  
        int v=edge[i].to;  
        if (edge[i].id==fa)<span style="font-family:KaiTi_GB2312;">//edge[i].id是那条边的编号,其中由一条无向边拆成两条有向边时,这两条边编号相同</span>
            continue;  
        //printf("%d %d\n",u,v);  
        if (!instack[v])  
        {  
            tarjan(v,edge[i].id);  
            if (low[u]>low[v])  
                low[u]=low[v];  
            if (low[v]>DFN[u])  
                bridge[res++]=edge[i].weight;  
        }  
        else if (low[u]>DFN[v])  
            low[u]=DFN[v];  
    }  
}

   简单解释为什么这样可以排除重边的情况:

   看图:


   比如现在从1开始搜索,选择A边到2,这时2开始循环边的时候,通过

dge[i].id==fa
   将A边排除,然后如果选择B边,发现1已经被压入栈,所以B边只能拿来修改修改low[u]的值了,这样A,B两条边都无法进行
if (low[v]>DFN[u])  
                bridge[res++]=edge[i].weight;
的判断,所以排除了A,B中有桥的情况,当然2,3亦是如此.


3.双连通分量

   定义:

    在无向连通图中,如果删除该图的任何一个结点都不能改变该图的连通性,则该图为双连通的无向图。一个连通的无向图是双连通的,当且仅当它没有关键点。换言之,双连通分量里任何2个顶点之间都至少有2条不相交的路径

   tarjan算法求边双连通分量(点双连通分量较为复杂,未完待续)

    直接看代码吧:

   

<span style="font-family:KaiTi_GB2312;font-size:18px;">void tarjan(int u,int fa)
{
	instack[u]=1;
	DFN[u]=low[u]=++index;
	for(int i=head[u];i!=-1;i=edge[i].next)
	{
		int v=edge[i].to;
		if(fa==edge[i].id)
			continue;
		if(!instack[v])
		{
			tarjan(v,edge[i].id);
			low[u]=min(low[u],low[v]);
		}
		else
			low[u]=min(DFN[v],low[u]);
	}
}

</span>

然后判断每个点的low值,同一个双连通分量里的low值相同。

之前我还怀疑这个做法的正确性,后来自己画了个图模拟了下,就明白了。

看图:

    我们假设从1开始搜索,依次为1-3-4-5-6,之后发现6-4这条边,选择以后更新low[6],回溯以后,low[5]也被更新,这样一来,4,5,6三个点的low值就都是4的DFN值了,也就是说,4是一个双连通分量的根;回溯到3,再到2,low[2]被更新,再回到3,low[3]被更新,这样一来,1,2,3的low值就是1的DFN值了,也就是说1另一个双连通分量的根,桥左右两边的顶点的low值不会被影响(这是我之前的疑问)

PS:如果想知道每个双连通分量里的具体点,可以设置一个栈,每次递归到一个点就压入栈,然后等这个点循环完其他所有边的时候,判断low值和DFN值是否相同,如果相同,说明这个点是一个双连通分量的根,那么就把这个点之上的点全部退出栈就行了(由于是边双连通分量,每个点只属于一个双连通分量)


二.强连通分量

   定义:

    有向图强连通分量在有向图G中,如果两个顶点vi,vj间(vi>vj)有一条从vi到vj的有向路径,同时还有一条从vj到vi的有向路径,则称两个顶点强连通(strongly connected)。如果有向图G的每两个顶点都强连通,称G是一个强连通图。有向图的极大强连通子图,称为强连通分量(strongly connected components)。

   tarjan算法求有向图的强连通分量:

    比较简单,而且我之前写过一篇关于强连通分量的,请参见:强连通分量tarjan算法

   

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值