dfnlow算法要点Word下载.docx
《dfnlow算法要点Word下载.docx》由会员分享,可在线阅读,更多相关《dfnlow算法要点Word下载.docx(13页珍藏版)》请在冰豆网上搜索。
强连通分量
桥
割顶
一、
引言
有关图论的几个问题,如求无向图[2]的桥[3]、求无向图的割顶[4]、求有向图[5]的强连通分量[6]等,都能够被几种不同的算法解决。
其中有一种算法,找到了这几个问题的共性,通过记录每个点的DFN值和LOW值,用大同小异的判定方式简洁的解决了这几个问题,思路巧妙具有针对性,把握住了问题的关键,值得借鉴。
本篇论文将回顾DFN-LOW算法在图论中的应用及解决相关问题时的思路。
二、
通过无向图桥的性质引出DFN-LOW算法对图进行遍历的方式
要了解图的一些性质,必然要对图进行遍历。
对图进行遍历的方式主要有两种,一种是宽度优先,一种是深度优先。
它们的本质区别在于宽度优先是按照每个点到源点的距离,一层一层的整体进行遍历。
对于每层点,每次将和它们相连且尚未被遍历的点放入下一层。
而深度优先则是每次从当前点遍历到一个与它相连且尚未被遍历的点,并由新的被遍历的点继续往下遍历,如果没有则回到上一个点重复上述操作。
由此可以看出,宽度优先遍历往往容易得到图的整体性质,深度优先遍历往往容易得到图的局部性质。
如右图,根据桥的定义可以知道,虽然去除连通图中被称作桥的边e<
3,4>
后会导
致原图被分割成两个连通图,整个原图连通性丧失,但其本质只是导致了e的两个端点3、4以及通过3、4而连通的点对间的连通性丧失,即3无法通过除了e以外的边集到达4,这相对而言属于局部性质,所以用深度优先进行图的遍历更加容易分析桥和图的连通性之间的关系。
此外,不管是何种遍历,在遍历过程中每个点都可能会被多次访问到,但是由于每个点都只会被遍历一次,所以由图中所有点和遍历边[7]组成的图G`必然是一棵树(以下图中用三角形表示子树),其中遍历边被称作树边(以下图中用实线表示),而原图中的非遍历边被称作非树边(以下图中用虚线表示)。
三、
通过求无向图的桥引入DFN-LOW算法
要判定某条边是否为桥,还需要对桥的性质进行进一步的挖掘。
那么直观的进行分析,如右图。
若在深度优先遍历中1号点通过了某条树边e遍历到了2号点,并接着以2号点为根遍历出了一棵子树,设u为子树中任意一点。
如果边e是桥,则等价于删除了e之后1号点和2号点会不连通,即2号点无法通过u访问到1号点或者比1号点更早被遍历到的点。
此时,e是否为桥便可以由2号点的性质来进行判定。
得到上述结论,便大致了解了DFN-LOW算法判定桥的思想,接下来对算法进行具体讲述。
在判定过程中,主要有两个量是判定的关键,因此定义dfn、low两个一维数组。
用dfn[i]记录编号为i的点的遍历序号,即第几个被遍历到的,用low[i]记录编号为i的点在接下来的遍历中可以访问到的最小序号。
那么由分析得知,对于通过树边e遍历出的点u,如果在接下来的遍历中u无法访问到序号比u更小的点,即dfn[u]等于low[u],那么e就为桥。
而根据low值的定义又可以知道u的low值可以由与u有边相连的点的low值计算出来。
根据这个思路,便可以写出整个算法的流程,下面以c++代码为例加以说明。
//edge_num[i]表示从i号点出发的边有多少条
//edge_lable[u][i]由u出去的第i条边的下标
//edge[u][i]表示由u点为起点的第i条边的终点编号。
voidbridge(longu,longe)//通过树边e遍历到了当前点u
{
longi,v;
tot++;
//累加遍历序号
dfn[u]=low[u]=tot;
//初始u点的dfn和low值都为遍历序号
for(i=1;
i<
=edge_num[u];
i++)//访问每个与u相连的点v
if(edge_lable[u][i]!
=e)//不能直接通过无向边e从u访问回序号小的点,否则算法失去意义
{
v=edge[u][i];
if(dfn[v]==0)//如果v尚未被遍历(设序号从1开始标记)
bridge(v,edge_lable[u][i]);
//那么对v进行遍历并传递相应的值
if(low[v]<
low[u])//v能访问到的点,u必然也能访问到,所以用low[v]更新low[u]
low[u]=low[v];
}
else
if(dfn[v]<
low[u])//如果v已经被遍历,那么u只能访问到v,用dfn[v]更新low[u]
low[u]=dfn[v];
}
if(low[u]==dfn[u])//如果遍历完以u为根的子树后low[u]仍然等于dfn[u]则说明e为桥
printf("
%ld\n"
e);
//输出e
四、
求无向图的割顶
通过求无向图的桥,DFN-LOW算法的判定思路已经展示的非常清晰。
而同时根据割顶的定义可以发现,桥的两个端点必然是割顶,删除了割顶等价于删除了桥,这说明了割顶和桥在性质上是非常类似的。
因此在求无向图的割顶的时候,依旧可以按照求桥时的思路进行判定,但是由于点和边的结构不同,所以一些细节也是必须要考虑的。
那么直观的分析一下割顶的特点,如右图。
1号点通过树边遍历到了2号点,然后以2号点为根遍历出了一个子树,设u为子树中的任意一点。
如果1号点是割顶,则等价于删除了1号点后2号点将与1号点以及比1号点更早被遍历到的点不连通,即2号点无法通过u访问到比1号点序号更小的点。
所以便可以得到结论,若v为u的一个儿子[8]且v的low值不小于u的dfn值,则u为图的割顶。
关于这个结论,有三个细节需要注意。
第一点,在结论中v的low值等于u的dfn值时结论也成立,这个很显然,看图分析即可得到。
第二点,如果u为深度优先遍历的根节点,那么只有当它有至少2个儿子v1、v2同时满足它们的low值不小于u的dfn值时结论才成立。
如右图,若v2子树不存在,那么删除u仅仅只是删除了图的一个“端点”,剩下的v1仍然是连通的。
所以判定根节点是否为割顶时要特殊考虑。
第三点,在第二点中,v1、v2必须是u的儿子,即必须是由u通过树边遍历出的点。
如右图,若v2和u有边相连但不是u的儿子,则v2必然在以v1为根的子树中,此时u只是图“边缘”上的一个点,删除u后v1、v2仍然连通,在图的连通性方面v1、v2也是等价的,所以应不对v2进行考虑。
根据以上讨论,便可以写出整个算法的流程,下面以c++代码为例加以说明。
voidcutpoint(longu,longe)//通过树边e遍历到了当前点u
longi,v,sum;
//初始u点的dfn和low值都为遍历序号
sum=0;
//初始化满足条件的儿子个数
if(dfn[u][g1]
==0)//如果v尚未被遍历(设遍历序号从1开始)
cutpoint(v,u);
low[u])//v能访问到的点u必然也能访问到,所以用low[v]更新low[u]
if(low[v]>
=dfn[u])//如果儿子v不能访问到比u序号更小的点
sum++;
//累加满足条件的儿子个数
if(dfn[v]<
}
if((sum>
=2)||//如果u不为根且有满足条件的儿子或者
(sum==1)&
&
(u!
=root))//u为根且有不少于2个满足条件的儿子则u为割顶
u);
//输出u
}
五、
求有向图的强连通分量
DFN-LOW算法在无向图中的应用是非常简捷的,因为无向图具有非常好的性质,即所有边都是无向边,因此有边相连的点集必然会形成一个连通块[9]。
而有向图则不然,即使一个连通图在边被定向之后也未必强连通,所以对于有向图来说,桥和割顶难以成为重心,相对的,在有向图中才有定义的强连通分量则成为了有向图中的重心。
本文讨论的强连通分量均默认为极大强连通分量[10]。
根据强连通分量的定义可以发现,它不同于桥或者割顶只是单个点或者边,而是点和边的集合,这就说明了求强连通分量会更加的复杂和烦琐。
但是即便如此,
仔细观察可以得知,它和桥或者割顶却有着极其类似的性质,如右图。
1号点由树边遍历到了2号点,并以2号点为根遍历出一棵子树。
如果这棵子树成为了一个强连通分量,则等价于2号点无法和1号点或者比1号点更早被遍历到的点强连通(否则该强连通分量不极大),即2号点无法通过有向边经过u访问到1号点或者比1号点序号更小的点。
这个结论和对桥进行判定时的结论简直如出一辙,因此完全可以用同样的思路来求强连通分量。
为了方便,在求强连通分量的过程中只记录每个强连通分量的点,在同一个强连通