有向图的强连通分量算法

这篇具有很好参考价值的文章主要介绍了有向图的强连通分量算法。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

有向图的强连通分量算法

  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 it

    InitStack_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 DFS
    int 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,YanWenMin
    for(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 stack
        while(popped_item!=u)
        {
            visit(G.vertices[popped_item].data);
            stk_status[popped_item]=false;
            Pop_Sq(stk,&popped_item); //Pop element from stack
            GetTop_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 代码文章来源地址https://www.toymoban.com/news/detail-463029.html


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;
        }
    }
}

到了这里,关于有向图的强连通分量算法的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 使用邻接矩阵实现有向图最短路径Dijkstra算法 题目编号:1136

    用邻接矩阵存储有向图,实现最短路径Dijkstra算法,图中边的权值为整型,顶点个数少于10个。 部分代码提示: #include #include using namespace std; const int MaxSize = 10; const int INF = 32767; class MGraph { public: MGraph(char a[], int n, int e); private: char vertex[MaxSize]; int arc[MaxSize][MaxSize]; int vertexNum,

    2024年02月01日
    浏览(67)
  • 基于邻接矩阵的有向图的广度优先遍历(BFS)和深度优先遍历(DFS)算法

    概念: 广度优先遍历算法是图的另一种基本遍历算法,其基本思想是尽最大程度辐射能够覆盖的节点,并对其进行访问。 以迷宫为例,广度优先搜索则可以想象成一组人一起朝不同的方向走迷宫,当出现新的未走过的路的时候,可以理解成一个人有分身术,继续从不同的方

    2024年02月06日
    浏览(49)
  • Dijkstra算法实现求有向图中一顶点到其余各个顶点的最短路径

    一、文章说明: C++语言实现; 有向图的存储结构为: 邻接矩阵; 这篇文章的代码是我根据B站UP主 懒猫老师 所写的,能成功运行,VS里面显示有很多警告。而且还可能存在有未调试出的BUG,请多多指教。 观看懒猫老师的视频后,才方便理解博主代码,不然可能理解起来会很吃

    2024年02月08日
    浏览(39)
  • 有向图的拓扑排序

    拓扑排序 。任意给定一个有向图,设计一个算法,对它进行拓扑排序。拓扑排序算法思想:a.在有向图中任选一个没有前趋的顶点输出;b.从图中删除该顶点和所有以它为尾的弧;c.重复上述a、b,直到全部顶点都已输出,此时,顶点输出序列即为一个拓朴有序序列;或者直到

    2024年02月09日
    浏览(35)
  • 公开游戏、基于有向图的游戏

    目录 〇,背景 一,公开游戏、策梅洛定理 1,公开游戏 2,策梅洛定理 3,非有向图游戏的公开游戏 力扣 486. 预测赢家(区间DP) 力扣 877. 石子游戏(退化贪心) 力扣 1140. 石子游戏 II(二维DP) 力扣 1406. 石子游戏 III(数列DP) 力扣 1563. 石子游戏 V(区间DP)  力扣 1686.

    2024年02月09日
    浏览(33)
  • 2023-8-29 有向图的拓扑排序

    题目链接:有向图的拓扑排序

    2024年02月11日
    浏览(28)
  • 真题详解(有向图)-软件设计(六十二)

    真题详解(极限编程)-软件设计(六十一) https://blog.csdn.net/ke1ying/article/details/130435971 CMM指软件成熟度模型,一般1级成熟度最低,5级成熟度最高,采用更高级的CMM模型可以提高软件质量。 初始:杂乱无章。 可重复级:建立基本的项目管理过程和跟踪费用项。 已定义(确定)

    2024年02月01日
    浏览(50)
  • 搜索与图论-有向图的拓扑序列

    有向图的拓扑序列就是图的广度优先遍历的一个应用。 若一个由图中所有点构成的序列 A 满足:对于图中的每条边 (x,y),x 在 A 中都出现在 y 之前,则称 A 是该图的一个 拓扑序列 。(起点在终点的前面) 拓扑序列是针对有向图,无向图是没有拓扑序列的。 有向无环图一定是

    2024年02月01日
    浏览(35)
  • 有向图的邻接表和邻接矩阵

    有向图的邻接表是一种常用的表示方法,用于表示图中各个节点之间的关系。在有向图中,每条边都有一个方向,因此邻接表中的每个节点记录了该节点指向的其他节点。 具体来说,有向图的邻接表由一个由节点和它们的邻居节点列表组成的集合构成。对于每个节点,邻接表

    2024年02月22日
    浏览(29)
  • 使用颜色检测有向图中的循环

    给定一个有向图,检查该图是否包含循环。如果给定的图形至少包含一个循环,您的函数应返回 true,否则返回 false。 例子:   输入:  n = 4, e = 6  0 - 1, 0 - 2, 1 - 2, 2 - 0, 2 - 3, 3 - 3  输出: 是  解释:    

    2024年02月03日
    浏览(32)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包