有向图的强连通分量算法

  1. 强连通分量定义
    在有向图中,某个子集中的顶点可以直接或者间接互相可达,那么这个子集就是此有向图的一个强连通分量,值得注意的是,一旦某个节点划分为特定的强连通分量后,此顶点不能在其它子树中重复使用,隐含了图的遍历过程和极大化原则。
    让我们用下图为例进行说明强连通分量含义:

    上面有向图中,包含四个强连通分量,每个强连通分量中都包含一个或多个连通路径,如果去掉其中任意顶点,那么其互相可达的性质就会被破坏。值得一提的是,遍历完成后,剩余的单个顶点本身也是强连通分量,如图中橙色所示。

  2. 如何求解某个图的强连通分量
    求解强连通分量的过程为施加条件的遍历过程,一般需要使用深度优先遍历过程。过程中需要屏蔽 不同强连通分量之间的互相干涉,根据屏蔽过程不同,一般分为两类算法:

  • Tarjan 算法,通过记录DFS对每个顶点先序遍历的时间戳,一般用disc[]数组表示,实现对访问次序的跟踪;然后引入low[]数组,记录从当前访问顶点 所能到达的最小(最低)顶点序列号;最后引入栈,来屏蔽不同强连通分量之间的互相干涉;
  • Kosaraju 算法,其本质为连个DFS遍历。第一个DFS针对原始图进行操作,第二个DFS针对边逆向的图进行操作,可以选择十字链表或邻接表的储存结构,通过逆向图,做到了不同强连通分量之间的互相屏蔽
  1. 算法分析
    3.1 Tarjan 算法
    a) Low 数组
    正式进入Tarjan算法之前,需要对low[]数组概念深入理解。low[]数组记录遍历过程中,当前顶点所能到达的最小的顶点序列号。如果当前顶点及其子树没有back edge, 那么low[]的值就是当前DFS访问的时间戳号;如果当前顶点或其子树存在back edge就需要在每个邻接点的递归退出后进行比较。
    让我们用示意图进行说明,
    DFS 以为0,1…6的次序进行遍历,边上带圆圈的值就是各个顶点的low[]具体值。我们以紫色的强连通分量为例,当遍历至顶点#6之前,1号顶点的low[1]=1;当遍历#6顶点后,需要比较#6和#0的low值,取最小值,作为#6顶点的low值,也即low[6]=min{low[0],low[6]}=0;可能大家有疑问,为什么不用#6顶点的low值和#2顶点比较呢? 这个问题其实涉及到下一个话题,暂且不表。然后继续回退low[1]=min{low[6],low[1]}=0,最后回到起始点low[0]={low[1],low[0]}=0.

如果起点选择适当,那么结合下一个栈话题,就可以求得图的所有连通分量。

上图的遍历方式从左边开始,最后完成强连通分量的求解。
那么如果从最右边的顶点开始,求解强连通分量,会有什么状况发生呢?

此时所有的low值均为0,显然不符合实际情况。那么就需要引入栈,对所有顶点进行出栈管理,避免不同强连通分量之间互相干扰。
b) 栈的作用
通过引入栈,可以有效客服随机顶点访问带来的问题,Tarjan算法通过维护栈,只有在栈里面的顶点才有机会更新low值。顶点在第一次DFS遍历的时候入栈,当找到一个完整的强连通分量,顶点出栈。那么何时出栈呢? 遍历回退,而且disc[u]==low[u]的时候,便表示整个最大的强连通分量环已经找到,便可以出栈直到u出栈。

c) Tarjan算法代码实现

//ALGraph is Adjacent list graph
//Vertex Type is the vertex name, it is char type
void find_scc(ALGraph G, void (*visit)(VertexType e))
{int i;int u;int disc[MAX_VERTEX_NUM];int low[MAX_VERTEX_NUM];bool stk_status[MAX_VERTEX_NUM];SqStack stk; //Stack definition by C language, you can define itInitStack_Sq(&stk);//Initialize disc, for(i=0;i<G.vexnum;i++){disc[i]=-1;low[i]=-1;stk_status[i]=false;}for(u=0;u<G.vexnum;u++){if(disc[u]==-1){find_component(G,u,disc,low,&stk,stk_status,visit);}}
}
void find_component(ALGraph G, int u, int *disc, int *low, SqStack *stk, bool *stk_status, void (*visit)(VertexType e))
{static int time =0; //time stamp, visiting sequence by DFSint popped_item;int v;int w;*(disc+u)=low[u]=++time;Push_Sq(stk,u); //Push u into the stack*(stk_status+u)=true; // Follow whether u is in the stack   //FirstAdjvex and NextAdjVex function, reference to //<<Data Structure>>, TsingHua University,YanWenMinfor(w=FirstAdjVex(G,u);w>=0;w=NextAdjVex(G,u,w)){if(disc[w]==-1){find_component(G,w,disc,low,stk,stk_status,visit);low[u]=minimum(low[u],low[w]);}else if(stk_status[w]){low[u]=minimum(low[u],disc[w]); //back edge in the graph}}popped_item=0;if(low[u]==disc[u]){GetTop_Sq(*stk,&popped_item); // Get top element from stackwhile(popped_item!=u){visit(G.vertices[popped_item].data);stk_status[popped_item]=false;Pop_Sq(stk,&popped_item); //Pop element from stackGetTop_Sq(*stk, &popped_item);}visit(G.vertices[popped_item].data);stk_status[popped_item] = false;Pop_Sq(stk, &popped_item);printf("\n");}
}
int minimum(int a, int b)
{return (a>b?b:a);
}

3.2 Kosaraju 算法
Kosaraju 算法本质是两个深度优先遍历,但是遍历的对象略有不同,初始遍历对象为原始连接图,第二次遍历对象为边反转图。第一次遍历过程中,在退出递归过程中,需要用栈或数组保存退出过程中的序列号,用作第二次遍历的基本顺序。
所以总结起来分为三步:
I) 对原始图进行DFS遍历,退出遍历前,用栈保存遍历的序号
II) 对原来的Graph进行反转操作,如果是十字链表的储存图,此步可以省略
III)在反转图上进行DFS遍历,求解出有向图的强连通分量
让我们用图形进行解释,

上图从0开始遍历,#7顶点结束;在退出遍历时候进行入栈操作,栈顶和栈底如上图所示。红色线条表示回退过程。

然后利用程序,对图进行反转操作(黑色箭头),反转后用红色直线箭头表示,如下图所示:

最后对反转图进行DFS遍历,求得强连通分量。

Kosaraju 代码


void DFS_traverse_original(ALGraph G, SqStack *stk)
{int i;int u;for(i=0;i<G.vexnum;i++){visited[i]=0;}for(u=0;u<G.vexnum;u++){if(!visited[u]){DFS_original(G,u,stk);}}
}void DFS_original(ALGraph G, int u, SqStack *stk)
{int v;int w;visited[u]=1;for(w=FirstAdjVex(G,u);w>=0;w=NextAdjVex(G,u,w)){if(!visited[w]){DFS_original(G,w,stk);}}Push_Sq(stk,u); //Here is the best part of this algorithm
}void DFS_traverse_transpose(ALGraph Rev_G, SqStack *stk, void (*visit)(VertexType e))
{int i;int u;for(i=0;i<Rev_G.vexnum;i++){visited[i]=0;}while(!StackEmpty_Sq(*stk)){Pop_Sq(stk,&u);if(!visited[u]){DFS_transpose(Rev_G,u,visit);printf("\n----------\n");}}
}void DFS_transpose(ALGraph Rev_G, int u, void (*visit)(VertexType e))
{int w;visited[u]=1;visit(Rev_G.vertices[u].data);for(w=FirstAdjVex(Rev_G,u);w>=0;w=NextAdjVex(Rev_G,u,w)){if(!visited[w]){DFS_transpose(Rev_G,w,visit);}}
}void Reverse_graph(ALGraph G, ALGraph *Rev_G)
{int i;int v;int w;ArcNode *p;Rev_G->vexnum=G.vexnum;Rev_G->arcnum=G.arcnum;Rev_G->kind=G.kind;for(i=0;i<Rev_G->vexnum;i++){Rev_G->vertices[i].data=G.vertices[i].data;Rev_G->vertices[i].firstarc=NULL;}for(v=0;v<G.vexnum;v++){for(w=FirstAdjVex(G,v);w>=0;w=NextAdjVex(G,v,w)){p=(ArcNode *)malloc(sizeof(ArcNode));p->info = NULL;p->nextarc=NULL;p->adjvex=v;p->nextarc=Rev_G->vertices[w].firstarc;Rev_G->vertices[w].firstarc=p;}}
}

有向图的强连通分量算法相关推荐

  1. C++Kosaraju找有向图的强连通分量算法(附完整源码)

    C++Kosaraju找有向图的强连通分量算法 C++Kosaraju找有向图的强连通分量算法完整源码(定义,实现,main函数测试) C++Kosaraju找有向图的强连通分量算法完整源码(定义,实 ...

  2. 算法提高课-图论-有向图的强连通分量-AcWing 367. 学校网络:强连通分量、tarjan算法

    文章目录 题目解答 题目来源 题目解答 来源:acwing 分析: 第一问:通过tarjan算法求出强连通分量并且缩点后,统计入度为0的点的个数p即可. 第二问,至少加几条边才能使图变成强连通分量?这 ...

  3. 算法提高课-图论-有向图的强连通分量-AcWing 1174. 受欢迎的牛:tarjan算法求强连通分量、tarjan算法板子、强连通图

    文章目录 题目解答 题目来源 题目解答 来源:acwing 分析: 强连通图:给定一张有向图.若对于图中任意两个结点x,y,既存在从x到y的路径,也存在从y到x的路径,则称该有向图是"强连通 ...

  4. 缩点(有向图的强连通分量)学习笔记

    缩点(有向图的强连通分量)学习笔记 1.什么是强连通分量?: 有向图强连通分量:在有向图G中,如果两个顶点vi,vj间(vi>vj)有一条从vi到vj的有向路径,同时还有一条从vj到vi的有向路 ...

  5. 有向图的强连通分量,割点与桥

    有向图的强连通分量 1.Tarjan /* Tarjan算法 复杂度O(N+M) */ #include<iostream> #include<stdio.h> #includ ...

  6. 强连通分量算法(2)

    Tarjan's strongly connected components algorithm Pku 2186,2553,1236,2762,都是可以用强连通分量算法来解决的.Kosaraju的算 ...

  7. 有向图的强连通分量(SCC)

    有向图的强连通分量(SCC) 1. 有向图的强连通分量原理 原理 强连通分量是针对有向图来说的.如下的讲解默认都是针对有向图的. 连通分量:对于一个有向图中的一些点来说,如果任意两点都能相互到达,则称 ...

  8. 求无向图的连通分量或有向图的强连通分量—tarjan()ccf高速公路

    概念定义: 在图论中,连通图基于连通的概念. 1. 连通(无向图): 若顶点Vi能通过路径到达Vj,那么称为Vi和Vj是连通的 对无向图:若从顶点Vi到顶点Vj有路径相连(当然从j到i也一定有路径), ...

  9. 【图论】—— 有向图的强连通分量

    给定有向图 ,若存在 ,满足从 出发能到达 中所有的点,则称 是一个"流图"( Flow Graph ),记为  ,其中, 称为流图的源点. 在一个流图  上从  进行深度优先遍历 ...

最新文章

  1. Adam那么棒,为什么还对SGD念念不忘 (2)—— Adam的两宗罪
  2. 中国小品演员都要卷舌?
  3. python比较excel表格内容并提取_python 实现excel数据的提取和整理
  4. 自学python需要多长时间-大家觉得自学python多久能学会?
  5. Silverlight简介
  6. windows 安装metis_Eigen+suitesparse for windows 安装
  7. 利用js实现 禁用浏览器后退| 去除上一个历史记录链接
  8. Spring 之autowired
  9. LinuxROS与Android哪个重要?
  10. 【Java】游戏小程序-超级玛丽(代码渗入)
  11. CAD小型软件开发二
  12. org.postgresql.util.PSQLException: 不支援 10 验证类型
  13. DeepFace: Closing the Gap to Human-Level Performance in Face Verification
  14. 循环的数学应用————21.特殊等式 xyz+yzz =532
  15. 《程序员修炼之道》读书笔记
  16. 3ds Max 材质贴图
  17. 数据结构之稀疏数组队列
  18. 【经验总结】如何做好复盘
  19. Java-Problems
  20. 机器人焊枪动作与编程实验_敲黑板 | 机器人是怎么完成任务的?这三种编程方式的区别你造吗...

热门文章

  1. UG建模教程,UGNX基础建模练习,拉伸命令-10
  2. 大数据多维分析平台的实践
  3. 一个比较齐全的汉字字库对应拼音 操作实现
  4. 单片机c51语言定义bool类型,C51单片机数据类型的具体定义及应用
  5. gerber 各层(Pads solder mask层和paste mask的区别)
  6. 华为模拟器 eNSP V100R003C00SPC100 Setup(全套官方珍藏版)
  7. 第九节、俄罗斯方块游戏_游戏优化(Qt5重新实现)
  8. C语言常见困惑、错误集锦(上) ——《C陷阱与缺陷》3篇
  9. ❤️导图整理数组6:四数组的四数之和,详解Counter类实现哈希表计数,力扣454❤️
  10. 【智能物流】罗戈研究院京东物流《数字化供应链综合研究报告》;AI智慧仓储和物流能为传统制造企业做这4件事