基于深度优先搜索算法,写出求无向图强连通分量量的算法

强连通分量之tarjan算法 - 黄廉政的专栏
- 博客频道 - CSDN.NET
1738人阅读
强连通分量问题通常可归纳为要求出强连通分量,然后通过缩点(将得出的每个连通分量视为一个点,然后继续构图,例如连通分量A有一个点有一条边指向连通分量B的一个点,那么在A上搭一条边到B,其他连通分量也以此类推)。求图的强连通分量一个算法为tarjan,在这博客中对tarjan的算法描述得非常的详细。我这里转载方便自己以后看
[有向图强连通分量]
在有向图G中,如果两个顶点间至少存在一条路径,称两个顶点强连通(strongly connected)。如果有向图G的每两个顶点都强连通,称G是一个强连通图。非强连通图有向图的极大强连通子图,称为强连通分量(strongly connected components)。
下图中,子图{1,2,3,4}为一个强连通分量,因为顶点1,2,3,4两两可达。{5},{6}也分别是两个强连通分量。
直接根据定义,用双向遍历取交集的方法求强连通分量,时间复杂度为O(N^2+M)。更好的方法是Kosaraju算法或Tarjan算法,两者的时间复杂度都是O(N+M)。本文介绍的是Tarjan算法。
[Tarjan算法]
Tarjan算法是基于对图深度优先搜索的算法,每个强连通分量为搜索树中的一棵子树。搜索时,把当前搜索树中未处理的节点加入一个堆栈,回溯时可以判断栈顶到栈中的节点是否为一个强连通分量。
定义DFN(u)为节点u搜索的次序编号(时间戳),Low(u)为u或u的子树能够追溯到的最早的栈中节点的次序号。由定义可以得出,
Low(u)=Min
Low(v),(u,v)为树枝边,u为v的父节点
DFN(v),(u,v)为指向栈中节点的后向边(非横叉边)
当DFN(u)=Low(u)时,以u为根的搜索子树上所有节点是一个强连通分量。
算法伪代码如下
DFN[u]=Low[u]=++Index
// 为节点u设定次序编号和Low初值
Stack.push(u)
// 将节点u压入栈中
for each (u, v) in E
// 枚举每一条边
if (v is not visted)
// 如果节点v未被访问过
// 继续向下找
Low[u] = min(Low[u], Low[v])
else if (v in S)
// 如果节点v还在栈内
Low[u] = min(Low[u], DFN[v])
if (DFN[u] == Low[u])
// 如果节点u是强连通分量的根
// 将v退栈,为该强连通分量中一个顶点
until (u== v)
接下来是对算法流程的演示。
从节点1开始DFS,把遍历到的节点加入栈中。搜索到节点u=6时,DFN[6]=LOW[6],找到了一个强连通分量。退栈到u=v为止,{6}为一个强连通分量。
返回节点5,发现DFN[5]=LOW[5],退栈后{5}为一个强连通分量。
返回节点3,继续搜索到节点4,把4加入堆栈。发现节点4向节点1有后向边,节点1还在栈中,所以LOW[4]=1。节点6已经出栈,(4,6)是横叉边,返回3,(3,4)为树枝边,所以LOW[3]=LOW[4]=1。
继续回到节点1,最后访问节点2。访问边(2,4),4还在栈中,所以LOW[2]=DFN[4]=5。返回1后,发现DFN[1]=LOW[1],把栈中节点全部取出,组成一个连通分量{1,3,4,2}。
至此,算法结束。经过该算法,求出了图中全部的三个强连通分量{1,3,4,2},{5},{6}。
可以发现,运行Tarjan算法的过程中,每个顶点都被访问了一次,且只进出了一次堆栈,每条边也只被访问了一次,所以该算法的时间复杂度为O(N+M)。
求有向图的强连通分量还有一个强有力的算法,为Kosaraju算法。Kosaraju是基于对有向图及其逆图两次DFS的方法,其时间复杂度也是O(N+M)。与Trajan算法相比,Kosaraju算法可能会稍微更直观一些。但是Tarjan只用对原图进行一次DFS,不用建立逆图,更简洁。在实际的测试中,Tarjan算法的运行效率也比Kosaraju算法高30%左右。此外,该Tarjan算法与也有着很深的联系。学习该Tarjan算法,也有助于深入理解求双连通分量的Tarjan算法,两者可以类比、组合理解。
求有向图的强连通分量的Tarjan算法是以其发明者命名的。Robert Tarjan还发明了求的Tarjan算法,以及求最近公共祖先的离线Tarjan算法,在此对Tarjan表示崇高的敬意。
附:tarjan算法的C++程序
void tarjan(int i)
DFN[i]=LOW[i]=++D
instack[i]=
Stap[++Stop]=i;
for (edge *e=V[i];e;e=e-&next)
if (!DFN[j])
tarjan(j);
if (LOW[j]&LOW[i])
LOW[i]=LOW[j];
else if (instack[j] && DFN[j]&LOW[i])
LOW[i]=DFN[j];
if (DFN[i]==LOW[i])
j=Stap[Stop--];
instack[j]=
Belong[j]=B
while (j!=i);
void solve()
Stop=Bcnt=Dindex=0;
memset(DFN,0,sizeof(DFN));
for (i=1;i&=N;i++)
if (!DFN[i])
tarjan(i);
强连通分量题目
poj1236 问题一是问最少给几个学校发文件能让所有学校都能经过传阅能阅读这个文件,其实就是求出强连通分量后缩点后入度为0的点的个数。因为只有入度为0的强连通分量中的学校无法经过其他点的传阅得到文件。第二问题问要最少增加几条边使得给任一个学校发文件经过传阅其他学校都能共享这个文件。这其实就是求缩点后的图的入度为0的点的个数与出度为0的个数中的较大者。(这里要注意的时候当只有一个点的时候入度出度都为0,但却不需要搭边)代码:
#include&iostream&
const int M = 105;
struct node
node *g[M];
int N,Stop,Bcnt,D
int DFN[M],LOW[M],instack[M],Stap[M],Belong[M];
void tarjan(int i)
DFN[i]=LOW[i]=++D
instack[i]=
Stap[Stop++]=i;
for (node *e=g[i];e;e=e-&next)
if (!DFN[j])
tarjan(j);
if (LOW[j]&LOW[i])
LOW[i]=LOW[j];
else if (instack[j] && DFN[j]&LOW[i])
LOW[i]=DFN[j];
if (DFN[i]==LOW[i])
j=Stap[--Stop];
instack[j]=
Belong[j]=B
while (j!=i);
void solve()
Stop=Bcnt=Dindex=0;
memset(DFN,0,sizeof(DFN));
for (i=0;i&N;i++)
if (!DFN[i])
tarjan(i);
int main()
int path[M][M],in[M],out[M],e;
memset(path,0,sizeof(path));
memset(in,0,sizeof(in));
memset(out,0,sizeof(out));
int i,r,ansin=0,ansout=0;
for(i=0;i&N;i++)
while(cin&&r && r!=0)
node *tmp=
tmp -&next = g[i];
for(i=0;i&N;i++)
for(tmp=g[i];tmp=tmp-&next)
if(Belong[i]!=Belong[e] && path[Belong[i]][Belong[e]]==0)
in[Belong[e]]++;
out[Belong[i]]++;
path[Belong[i]][Belong[e]]=1;
for(i=0;i&Bi++)
if(in[i]==0)
if(out[i]==0)
if(Bcnt==1)
ansout = ansout&ansin ? ansout:
cout&&ansin&&
cout&&ansout&&
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:32665次
排名:千里之外
原创:34篇
转载:13篇
评论:15条
(1)(3)(8)(4)(10)(2)(2)(1)(1)(4)(12)算法导论 第22章 图算法 22.3 深度优先搜索 - 幸福在路上
- 博客频道 - CSDN.NET
2592人阅读
&深搜策略:在深搜过程中,对于最新发现的顶点,如果它还有以此为起点的而未探测到的边,就沿此边继续探测下去。当顶点v的所有边都已被探测过后,搜索将回溯到发现顶点v有起始点的那些边。
时间戳:当顶点v第一次被发现时记录下第一个时间戳d[v],当结束检查v的邻接表时,记录下第二个时间戳f[v]。v在d[v]时刻前是白色的,在时刻d[v]和f[v]之间是灰色的,在时刻f[v]之后是黑色的。
括号定理,后裔区间的嵌套,白色路径定理
边的分类:(1)树边(2)反向边(3)正向边(4)交叉边
对无向图G进行深度搜索时,G的每一条边,要么是树边,要么是反向边。
1.Link_Graph.h
#include &iostream&
#include &queue&
#define N 100
#define WHITE 0
#define GRAY 1
#define BLACK 2
#define NONE 0
#define TREE 1
#define BACK 2
#define FORWARD 3
#define CROSS 4
struct Edge
Edge(int s, int e, int v)
:start(s),end(e),value(v),type(NONE),next(NULL){}
struct Vertex
int d,//第一次被发现的时间和结束检查的时间
//顶点的颜色
//指向遍历结果的父结点
Edge *//指向以该顶点为起点的下一条边
Vertex():color(WHITE),p(0),head(NULL){};
class Link_Graph
Vertex *V;
Link_Graph(int num):n(num)
V = new Vertex[n+1];
~Link_Graph(){delete []V;}
void AddSingleEdge(int start, int end, int value = 1)
Edge *NewEdge = new Edge(start, end, value);
if(V[start].head == NULL || V[start].head-&end & end)
NewEdge-&next = V[start].
V[start].head = NewE
Edge *e = V[start].head, *pre =
while(e != NULL && e-&end & end)
if(e && e-&end == end)
delete NewE
NewEdge-&next =
pre-&next = NewE
void AddDoubleEdge(int a, int b, int value = 1)
AddSingleEdge(a, b, value);
AddSingleEdge(b, a, value);
void DeleteSingleEdge(int start, int end)
Edge *e = V[start].head, *pre =
while(e && e-&end & end)
if(e == NULL || e-&end & end)
if(e == V[start].head)
V[start].head = e-&
pre-&next = e-&
void DeleteDoubleEdge(int a, int b)
DeleteSingleEdge(a, b);
DeleteSingleEdge(b, a);
void DFS();
void DFS_Visit(int u);
void Print_Vertex_Time();
void Print_Edge_Type();
void Link_Graph::DFS()
//对每个顶点初始化
for(u = 1; u &= u++)
V[u].color = WHITE;
//时间戳初始化
//依次检索V中的顶点,发现白色顶点时,调用DFS_Visit访问该顶点
for(u = 1; u &= u++)
if(V[u].color == WHITE)
DFS_Visit(u);
void Link_Graph::DFS_Visit(int u)
//将u置为灰色
V[u].color = GRAY;
//使全局变量time增值
time++;
//将time的新值记录为发现时间
//如果顶点为白色
if(V[v].color == WHITE)
//递归访问顶点
DFS_Visit(v);
e-&type = TREE;
else if(V[v].color == GRAY)
e-&type = BACK;
else if(V[v].color == BLACK)
if(V[u].d & V[v].d)
e-&type = FORWARD;
e-&type = CROSS;
//以u为起点的所有边都被探寻后,置u为黑色
V[u].color = BLACK;
//并将完成时间记录在f[u]中
time++;
void Link_Graph::Print_Vertex_Time()
for(i = 1; i &= i++)
//因为书中的例子中用从q开始的字母编号的,所以输出的时候有这样一个转换
cout&&char('p'+i)&&':';
cout&&V[i].d&&' '&&V[i].f&&
void Link_Graph::Print_Edge_Type()
for(i = 1; i &= i++)
Edge *e = V[i].
cout&&char(e-&start+'p')&&&-&&&&char(e-&end+'p')&&&: &;
switch (e-&type)
case TREE:
cout&&&树边&&&
case BACK:
cout&&&反向边&&&
case FORWARD:
cout&&&正向边&&&
case CROSS:
cout&&&交叉边&&&
2.main.cpp
#include &iostream&
#include &Link_Graph.h&
int main()
char start,
//输入点的个数和边的个数
cin&&n&&m;
//构造一个空的图
Link_Graph *G = new Link_Graph(n);
for(i = 1; i &= i++)
cin&&start&&
G-&AddSingleEdge(start-'p', end-'p');
//深度优先搜索
//输出每个顶点的第一次访问时间和访问结束的时间
G-&Print_Vertex_Time();
//输出每条边的类型
G-&Print_Edge_Type();
3.测试数据
书P335,图22-6
4.输出结果
关键是“任何时刻”
q-&s: 树边
q-&t: 树边
q-&w: 正向边
r-&u: 树边
r-&y: 交叉边
s-&v: 树边
t-&x: 树边
t-&y: 树边
u-&y: 交叉边
v-&w: 树边
w-&s: 反向边
x-&z: 树边
y-&q: 反向边
z-&x: 反向边
(u(v(y(xx)y)v)u)(w(zz))w)
参考1楼所给的答案:
E={(w,u), (u,w), (w,v)}
有一条从u到v的路径,即u-&w-&v
DFS的顺序为w,u,u,v,v,w,d[u]=2,d[v]=4,d[u]&d[v]
得到的森林中,u和v都是w的后裔
如图所示,(1)有一条从u到v的路径
貌似不用改
联通分支的数量用ceil表示,代码修改如下:
void Link_Graph::DFS()
int u, ceil = 0;
//对每个顶点初始化
for(u = 1; u &= u++)
V[u].color = WHITE;
//时间戳初始化
//依次检索V中的顶点,发现白色顶点时,调用DFS_Visit访问该顶点
for(u = 1; u &= u++)
if(V[u].color == WHITE)
ceil++;
DFS_Visit(u, ceil);
void Link_Graph::DFS_Visit(int u, int ceil)
//将u置为灰色
V[u].color = GRAY;
//使全局变量time增值
time++;
//将time的新值记录为发现时间
//如果顶点为白色
if(V[v].color == WHITE)
//递归访问顶点
DFS_Visit(v, ceil);
e-&type = TREE;
else if(V[v].color == GRAY)
e-&type = BACK;
else if(V[v].color == BLACK)
if(V[u].d & V[v].d)
e-&type = FORWARD;
e-&type = CROSS;
//以u为起点的所有边都被探寻后,置u为黑色
V[u].color = BLACK;
V[u].ceil =
//并将完成时间记录在f[u]中
time++;
单连通图没有正向边
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:406476次
积分:7436
积分:7436
排名:第1148名
原创:306篇
转载:22篇
评论:535条
文章:26篇
阅读:72750
(5)(5)(3)(1)(4)(1)(2)(3)(1)(4)(1)(1)(2)(1)(2)(4)(1)(3)(3)(4)(18)(37)(22)(21)(32)(16)(3)(12)(67)(49) 上传我的文档
 下载
 收藏
所有文档均来自于网络,所搜集文档几乎涵盖了所有行业,均严格按照道客巴巴->文档分类->所有文档->分类。对每篇文档详细释义做出了合理推敲,并对其精确划分了类别。例:合伙协议简洁版 ->(标题)->所有文档->(法律文献)->(分类)->合同范本。旨在让大家把搜索到的文档对照标题,参照分类。一看便一目了然成竹在胸,放心下载,安心使用。笔者主要搜集了一些常用文档,如论文,工作总结,合同协议,规章制度,报告,方案,课件,试题,试卷,答案,职业资格考试,策划营销.....等等一些教育学习,办公写作,商业经营,实用性极强的文档,您若觉得对您日常学习,商业经营,社交沟通,办公写作有所帮助的话就关注我吧。
 下载此文档
正在努力加载中...
图的深度优先搜索遍历算法分析及其应用
下载积分:1000
内容提示:图的深度优先搜索遍历算法分析及其应用
文档格式:PDF|
浏览次数:53|
上传日期: 16:32:32|
文档星级:
该用户还上传了这些文档
图的深度优先搜索遍历算法分析及其应用.PDF
官方公共微信汇聚2000万达人的兴趣社区下载即送20张免费照片冲印
扫码下载App
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!&&|&&
认清事情的本质!
LOFTER精选
网易考拉推荐
用微信&&“扫一扫”
将文章分享到朋友圈。
用易信&&“扫一扫”
将文章分享到朋友圈。
阅读(53949)|
用微信&&“扫一扫”
将文章分享到朋友圈。
用易信&&“扫一扫”
将文章分享到朋友圈。
历史上的今天
loftPermalink:'',
id:'fks_084068',
blogTitle:'9、深度优先算法,图的遍历',
blogAbstract:'
{list a as x}
推荐过这篇日志的人:
{list a as x}
{if !!b&&b.length>0}
他们还推荐了:
{list b as y}
转载记录:
{list d as x}
{list a as x}
{list a as x}
{list a as x}
{list a as x}
{if x_index>4}{break}{/if}
${fn2(x.publishTime,'yyyy-MM-dd HH:mm:ss')}
{list a as x}
{if !!(blogDetail.preBlogPermalink)}
{if !!(blogDetail.nextBlogPermalink)}
{list a as x}
{if defined('newslist')&&newslist.length>0}
{list newslist as x}
{if x_index>7}{break}{/if}
{list a as x}
{var first_option =}
{list x.voteDetailList as voteToOption}
{if voteToOption==1}
{if first_option==false},{/if}&&“${b[voteToOption_index]}”&&
{if (x.role!="-1") },“我是${c[x.role]}”&&{/if}
&&&&&&&&${fn1(x.voteTime)}
{if x.userName==''}{/if}
网易公司版权所有&&
{list x.l as y}
{if defined('wl')}
{list wl as x}{/list}

我要回帖

更多关于 强连通分量 tarjan 的文章

 

随机推荐