算法学习有向图的强连通分量Word格式.docx
- 文档编号:18601029
- 上传时间:2022-12-29
- 格式:DOCX
- 页数:5
- 大小:59.84KB
算法学习有向图的强连通分量Word格式.docx
《算法学习有向图的强连通分量Word格式.docx》由会员分享,可在线阅读,更多相关《算法学习有向图的强连通分量Word格式.docx(5页珍藏版)》请在冰豆网上搜索。
ifflag[i]=0thendfs(i);
而不是直接的dfs
(1),因为节点1并不一定能到达所有节点。
这是初学者(我?
)常犯的一个错误。
当然,DFS进行的顺序并不一定是按节点编号顺序。
一般按节点编号顺序DFS只是为了方便,有时我们必须以不同的顺序进行DFS(比如下面将要谈到的Kosaraju算法)。
时间戳与点的分类设置全局变量time,初始值为0,当遇到一个事件点的时候就++1。
时间点分为两种:
发现某一节点和结束某一节点(即在DFS过程的开头和结尾)。
发现节点x时,发现时间戳d[x]=time;
结束节点x时,结束时间戳f[x]=time。
根据时间戳状态的不同可以将节点进行分类:
白点==没有时间戳的节点==未遍历的节点灰点==仅有发现时间戳的节点==正在遍历以此节点为根的子树的节点黑点==有发现时间戳和结束时间戳的节点==完成的节点注意:
点的分类随time增长而变化,且DFS结束后所有点都是黑点,所以用一个全局变量保存点的分类没有意义。
DFS过程中可以直接通过时间戳得知点的分类。
边的分类在DFS过程中所有顶点和经过的边形成了一棵DFS树。
根据图中的边在DFS树中的位置和发现此边时入节点的分类(这里说的是有向图,每条边有出节点和入节点)将边分为四类:
树枝==DFS树中的边==入节点为白点反向边==某一节点x到其DFS树中祖先y的边==入节点为灰点正向边==某一节点x到其DFS树中后代y的边==入节点为黑点==d[x]>
d[y]横叉边==某一节点x到DFS树中另一子树上节点y的边==入节点为黑点==d[x]<
d[y]如下图:
若以节点序号顺序进行DFS,则形成下面一棵DFS树(粗边是树枝,其他类型的边在旁边有说明)
横叉边在图中是只能向左的,通过DFS的过程可以知道这一点。
这对我们设计算法很有帮助。
注意,边的分类与点的分类完全不同。
点的分类随着时间变化,而边的分类是不变的。
所以我们需要用一个全局变量保存边的分类。
在后面的程序中,我们用kl[i,j]表示i到j的边的分类,树枝、反向边、正向边、横叉边分别用1/2/3/4表示。
强连通分量一个有向图中,如果节点i能够通过一些边到达节点j,就简写成i能到达j。
如果对于任意两个节点i,j均有i能到达j或j能到达i,则说此图是连通的。
如果对于任意两个节点i,j均有i能到达j且j能到达i,则说此图是强连通的。
对于一个无向图,说强联通没有意义,因为此时强连通就是连通。
而对于一个有向图,它不一定是强连通的,但可以分为几个极大的强连通子图(“极大”的意思是再加入任何一个顶点就不满足强连通了)。
这些子图叫做这个有向图的强连通分量。
在上图中,强连通分量是A{1},B{2,4},C{3,5,6,7}。
在一个强连通分量中的节点由于有着相似的拓扑性质,所以我们可以将其紧缩为一个节点(这让我想到了什么?
化学里的“族”),于是就大大减小了图的规模。
比如上图紧缩为A,B,C三个节点后,整个图就成为了B←A→C的简单形式。
所以强连通分量是一个很有意义的东西。
然而如果根据定义,对每个节点进行一次O(n2)的DFS以求出它所能到达的顶点的话,整个算法的时间复杂度就是迟钝的O(n3)。
下面将介绍两种用O(n2)求强连通分量的算法:
Kosaraju算法和Tarjan算法。
Kosaraju算法Kosaraju算法基于以下思想:
强连通分量一定是某种DFS形成的DFS树森林。
Kosaraju算法给出了更具体的方式:
①任意进行一次DFS,记录下每个节点的结束时间戳f[i]。
②按f[i]的大小对节点进行排序(从小到大)。
③以②的排序结果逆序对原图的逆向图进行一次DFS,所得的DFS树森林即为强连通分量。
这次DFS可以用Floodfill进行,把每个强连通分量标上不同序号。
(这就是OI界传说中的“求强连两遍DFS的算法”)比如上图,我们可以得到:
d[1]=1
f[1]=14d[2]=2
f[2]=5d[3]=6
f[3]=13d[4]=3
f[4]=4d[5]=7
f[5]=8d[6]=9
f[6]=12d[7]=10f[7]=11根据f[i]排序得:
④②⑤⑦⑥③①(发现了什么?
就是后序遍历),再按照逆序对原图的逆向图进行一次DFS即可程序:
vara:
array[1..1000,1..1000]oflongint;
b,flag:
array[1..1000]oflongint;
n,i,j,m,k:
if(flag[j]=0)and(a[x,j]>
0)thendfs(j);
inc(m);
b[m]:
=x;
procedurefill(x,k:
=k;
=ndownto1do
if(flag[b[j]]=0)and(a[b[j],x]>
0)thenfill(b[j],k);
begin
readln(n);
begin
=1tondoread(a[i,j]);
readln;
end;
m:
=0;
fillchar(flag,sizeof(flag),0);
ifflag[i]=0thendfs(i);
k:
ifflag[b[i]]=0then
inc(k);
fill(b[i],k);
=1tokdo
ifflag[j]=ithenwrite(j,'
'
);
writeln;
end.
kosaraju算法小结
kosaraju算法是用于求有向图的强连通分量的算法之一
步骤概要:
1.DFS有向图G,并以后根序记录节点
2.把存在于记录集中且最后访问节点作为起点,DFS反图GT,并以先根序把节点从记录中剔除;
3.若此次不能DFS反图GT所有节点,则重复步骤2,直到所有节点都被剔除出记录;
每次剔除掉的节点集即为原有向图G的一个强连通分量
简要证明:
1.第一次DFS有向图G时,最后记录下的节点必为最后一棵生成树的根节点。
证明:
假设最后记录下节点不是树根,则必存在一节点为树根,且树根节点必为此节点祖先;
而由后根序访问可知祖先节点比此节点更晚访问,矛盾;
原命题成立
2.第一次DFS的生成森林中,取两节点A、B,满足:
B比A更晚记录下,且B不是A的祖先(即在第一次DFS中,A、B处于不同的生成树中);
则在第二次DFS的生成森林中,B不是A的祖先,且A也不是B的祖先(即在第二次DFS中,A、B处于不同的生成树中)。
假设在第二次DFS的生成森林中,B是A的祖先,则反图GT中存在B到A路径,即第一次DFS生成森林中,A是B的祖先,则A必比B更晚记录下,矛盾;
假设在第二次DFS的生成森林中,A是B的祖先,则反图GT中存在A到B路径,即第一次DFS生成森林中,B是A的祖先,矛盾;
3.按上述步骤求出的必为强连通分量证明:
首先,证明2保证了第二次DFS中的每一棵树都是第一次DFS中的某棵树或某棵树的子树。
其次,对于第二次DFS中的每棵树,第一次DFS保证了从根到其子孙的连通性,第二次DFS保证了根到子孙的反向连通性(即子孙到根的连通性);
由此,此树中的每个节点都通过其根相互连通。
Tarjan算法Tarjan算法只要一遍DFS,效率高于Kosaraju。
它在技术上有了很大改进。
它基于的思想是:
强连通分量是DFS树中的子树(无论你如何进行DFS)。
Tarjan算法的过程是:
①在DFS树中,设low[x]是x或x的后代能够达到的最高的祖先。
初始化时low[x]设为x。
②进行DFS,在结束节点x时计算low[x]。
计算的方法是:
找出x能够到达的所有节点i,应保证low[i]是x的祖先,即low[i]是灰点。
low[x]=highest(low[i]),即d[low[i]]最小。
③若low[x]=x,则以x为根的子树就是一个强连通分量,输出并将其从树中删去。
比如上图(又是上图。
。
没办法,图片空间有限制),我们依次得出:
low[4]=low[2]=2low[2]=low[4]=2
//{4,2}为强连通分量low[5]=low[3]=3low[7]=low[5]=3low[6]=low[7]=3low[3]=low[6]=3
//{5,7,6,3}为强连通分量low[1]=1
//{1}为强连通分量感谢ChenGang同学的提醒,我原来的程序中的输出有些问题。
现在采用堆栈的方法,dfs到某一节点时将其入栈,当low[x]=x时不断出栈,直到将x出栈为止,因为x是这个强连通分量的根。
程序:
vara,kl:
d,f,low,stack:
i,j,n,time,h:
vari:
inc(time);
d[x]:
=time;
inc(h);
stack[h]:
fori:
ifa[x,i]>
0then
ifd[i]=0then
kl[x,i]:
dfs(i);
if(d[i]>
0)and(f[i]=0)thenkl[x,i]:
=2;
iff[i]>
ifd[x]>
d[i]thenkl[x,i]:
=3elsekl[x,i]:
=4;
f[x]:
if(f[low[i]]=0)and(d[low[i]]<
d[low[x]])and(d[low[i]]>
0)then
low[x]:
=low[i];
iflow[x]=xthen
whilestack[h]<
>
xdo
write(stack[h],'
dec(h);
writeln(stack[h]);
fillchar(d,sizeof(d),0);
=1tondolow[i]:
=i;
time:
fillchar(stack,sizeof(stack),0);
h:
ifd[i]=0thendfs(i);
有人会问,这里边的分类用不到啊?
我会郑重地回答:
就是没用。
实际上,原因在于:
BYVoid神牛的Blog里也讲到了Tarjan算法(图文并茂啊。
我就是看这个学的),而里面涉及了边的分类却没有给予解释,对于初学者(又是我?
)不利。
所以我就写了这些。
以下无正文
仅供个人用于学习、研究;
不得用于商业用途。
толькодлялюдей,которыеиспользуютсядляобучения,исследованийинедолжныиспользоватьсявкоммерческихцелях.
notforcommercialuse.
Nurfü
rdenpersö
nlichenfü
rStudien,Forschung,zukommerziellenZweckenverwendetwerden.
Pourl'
é
tudeetlarechercheuniquementà
desfinspersonnelles;
pasà
desfinscommerciales.
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 算法 学习 连通 分量