算法学习有向图的强连通分量.docx

上传人:b****5 文档编号:5613921 上传时间:2022-12-29 格式:DOCX 页数:5 大小:59.84KB
下载 相关 举报
算法学习有向图的强连通分量.docx_第1页
第1页 / 共5页
算法学习有向图的强连通分量.docx_第2页
第2页 / 共5页
算法学习有向图的强连通分量.docx_第3页
第3页 / 共5页
算法学习有向图的强连通分量.docx_第4页
第4页 / 共5页
算法学习有向图的强连通分量.docx_第5页
第5页 / 共5页
亲,该文档总共5页,全部预览完了,如果喜欢就下载吧!
下载资源
资源描述

算法学习有向图的强连通分量.docx

《算法学习有向图的强连通分量.docx》由会员分享,可在线阅读,更多相关《算法学习有向图的强连通分量.docx(5页珍藏版)》请在冰豆网上搜索。

算法学习有向图的强连通分量.docx

算法学习有向图的强连通分量

Forpersonaluseonlyinstudyandresearch;notforcommercialuse

算法学习:

有向图的强连通分量

DFSDFS就是深度优先搜索,裸的DFS是很启蒙的一个东西,所以就不废话了,直接给出它的程序:

proceduredfs(x:

longint); varj:

longint; begin flag[x]:

=1; forj:

=1tondo  if(a[x,j]=1)and(flag[j]=0)then   dfs(j); end;其中flag是是否遍历过的标记,a是邻接矩阵。

当然,在主程序中需要有这一句:

fori:

=1tondo 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]

若以节点序号顺序进行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:

longint;

proceduredfs(x:

longint); varj:

longint; begin flag[x]:

=1; forj:

=1tondo  if(flag[j]=0)and(a[x,j]>0)thendfs(j); inc(m); b[m]:

=x; end;

procedurefill(x,k:

longint); varj:

longint; begin flag[x]:

=k; forj:

=ndownto1do  if(flag[b[j]]=0)and(a[b[j],x]>0)thenfill(b[j],k); end;

begin

 readln(n); fori:

=1tondo begin  forj:

=1tondoread(a[i,j]);  readln; end; m:

=0; fillchar(flag,sizeof(flag),0); fori:

=1tondo ifflag[i]=0thendfs(i);

 fillchar(flag,sizeof(flag),0); k:

=0; fori:

=ndownto1do ifflag[b[i]]=0then  begin   inc(k);   fill(b[i],k);  end;

 fori:

=1tokdo begin  forj:

=1tondo   ifflag[j]=ithenwrite(j,'');  writeln; end;

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:

array[1..1000,1..1000]oflongint;   d,f,low,stack:

array[1..1000]oflongint;   i,j,n,time,h:

longint;

proceduredfs(x:

longint); vari:

longint; begin inc(time); d[x]:

=time; inc(h); stack[h]:

=x; fori:

=1tondo  ifa[x,i]>0then   begin    ifd[i]=0then     begin      kl[x,i]:

=1;      dfs(i);     end;    if(d[i]>0)and(f[i]=0)thenkl[x,i]:

=2;    iff[i]>0then     begin      ifd[x]>d[i]thenkl[x,i]:

=3elsekl[x,i]:

=4;     end;   end; inc(time); f[x]:

=time; fori:

=1tondo  ifa[x,i]>0then   if(f[low[i]]=0)and(d[low[i]]0)then    low[x]:

=low[i]; iflow[x]=xthen  begin   whilestack[h]<>xdo    begin     write(stack[h],'');     dec(h);    end;   writeln(stack[h]);   dec(h);  end; end;

begin

 readln(n); fori:

=1tondo begin  forj:

=1tondoread(a[i,j]);  readln; end;

 fillchar(d,sizeof(d),0); fillchar(d,sizeof(d),0); fori:

=1tondolow[i]:

=i; time:

=0; fillchar(stack,sizeof(stack),0); h:

=0; fori:

=1tondo ifd[i]=0thendfs(i);

end.

有人会问,这里边的分类用不到啊?

我会郑重地回答:

就是没用。

实际上,原因在于:

BYVoid神牛的Blog里也讲到了Tarjan算法(图文并茂啊。

我就是看这个学的),而里面涉及了边的分类却没有给予解释,对于初学者(又是我?

)不利。

所以我就写了这些。

 

有人会问,这里边的分类用不到啊?

我会郑重地回答:

就是没用。

实际上,原因在于:

BYVoid神牛的Blog里也讲到了Tarjan算法(图文并茂啊。

我就是看这个学的),而里面涉及了边的分类却没有给予解释,对于初学者(又是我?

)不利。

所以我就写了这些。

以下无正文

仅供个人用于学习、研究;不得用于商业用途。

 толькодлялюдей,которыеиспользуютсядляобучения,исследованийинедолжныиспользоватьсявкоммерческихцелях. 

Forpersonaluseonlyinstudyandresearch;notforcommercialuse.

NurfürdenpersönlichenfürStudien,Forschung,zukommerziellenZweckenverwendetwerden.

Pourl'étudeetlarechercheuniquementàdesfinspersonnelles;pasàdesfinscommerciales.

Forpersonaluseonlyinstudyandresearch;notforcommercialuse

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 工程科技 > 能源化工

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1