Tarjan 算法求有向图强连通分量+tarjan模板

Tarjan 算法求有向图强连通分量+tarjan模板

强连通分量定义

有向图强连通分量:在有向图G中,如果两个顶点vi,vj间(vi>vj)有一条从vi到vj的有向路径,同时还有一条从vj到vi的有向路径,则称两个顶点强连通(strongly connected)。如果有向图G的每两个顶点都强连通,称G是一个强连通图。有向图的极大强连通子图,称为强连通分量(strongly connected components)。

用来求此问题的算法有两种,一种是Kosaraju算法,其大致思路是:

step1:对原图G进行深度优先遍历,记录每个节点的离开时间。

step2:选择具有最晚离开时间的顶点,对反图GT进行遍历,删除能够遍历到的顶点,这些顶点构成一个强连通分量。

step3:如果还有顶点没有删除,继续***step2***,否则算法结束。

另一种就是本文要讲的***Tarjan***算法

Tarjan算法的基本思路

基本思路

首先考虑强连通分量的性质,即存在一条回路能从初始点又回到初始点。在这个查找的过程中,可以对经过的顶点标记,当发现某一节点连向的点正好以及被标记过,则说明找到了一条回路,而这个回路上的所有点构成一个强连通分量。为了保存这个强连通分量,我们需要知道这条路上有哪些点,而此时,就是一种适合该算法的数据结构。对于每次搜索的点,我们都加入栈中,遇到回路时,在把栈中的元素逐个弹出,记录它们的起始结点,直到栈中弹出的元素正好是起始结点时,结束弹栈,继续搜索其它强连通分量。在这个过程中,所有的点和都有的边都被遍历了一次,所以最终的时间复杂度为 O(N+E)

实现过程需要以下几种数据类型:
记录搜索序的数组dfn;
记录所属强连通编号的数组low
判断某顶点是否在栈中的数组instack
一个栈(stack)存储搜索路径;

图解流程

img

从顶点1开始搜索,把遍历到的节点加入栈中。搜索到顶点6时,发现顶点6没有出度了,然后进而判断发现dfn[6]==low[6],即找到了一个强连通分量。退栈到_u=v_为止,{6}为一个强连通分量。

img

扫描二维码关注公众号,回复: 11036586 查看本文章

返回顶点5,发现dfn[5]==low[5],即找到了一个强连通分量{5},退栈

img

返回顶点3,我们发现定点3还有未标记出度,故继续搜索到顶点4,顶点4入栈。发现顶点4有出度连接点1,此时进行instack判断,点1还在栈中,所以更新顶点4的low值 low[4]=dfn[1]。顶点4的另一个相连点节点6已经出栈,故顶点4退栈到顶点3,重复更新low[i]=dfn[1]

img

至此结束,共有三个强连通分量{1,3,4,2},{5},{6}

伪代码

Tarjan(u)
{
    dfn[u]=low[u]=++idx //初始化时间戳和low值                       
    stack.push(u)          //入栈
    instack[u]=true  //记录入栈
    for each (u, v) in E     //遍历每一条边                  
        if (v is not visted)        //如果没有遍历过      
            tarjan(v)              //向下遍历     
            low[u] = min(low[u], low[v]) //更新low值
        else if (instack[v])        //如果在队列中           
            low[u] = min(low[u], dfn[v]) //更新low值
    if (dfn[u] == low[u])              //如果该点是整个强连通分量的最小根       
        repeat
            v = stack.pop                
            print v			//打印
        until (u== v)
}

代码模板

#include <cstdio>
#include <algorithm>
#include <string.h>
using namespace std;
const int N = 1001;
struct node
{
    int v, next;
} edge[N];

int dfn[N], low[N];
int stack[N], h[N], visit[N], cnt, tot, idx;
void add(int x, int y)
{
    edge[++cnt].next = h[x];
    edge[cnt].v = y;
    h[x] = cnt;
}

void tarjan(int x) //代表第几个点在处理。递归的是点。
{
    dfn[x] = low[x] = ++tot; // 新进点的初始化。
    stack[++idx] = x;      //入栈
    visit[x] = 1;            //表示在栈里
    for (int i = h[x]; i != -1; i = edge[i].next)
    {
        if (!dfn[edge[i].v])
        {                                         //如果没访问过
            tarjan(edge[i].v);                    //往下进行延伸,开始递归
            low[x] = min(low[x], low[edge[i].v]); //递归出来,比较谁是谁的儿子/父亲,就是树的对应关系,涉及到强连通分量子树最小根的事情。
        }
        else if (visit[edge[i].v])
        {                                         //如果访问过,并且还在栈里。
            low[x] = min(low[x], dfn[edge[i].v]); //比较谁是谁的儿子/父亲。就是链接对应关系
        }
    }
    if (low[x] == dfn[x]) //发现是整个强连通分量子树里的最小根。
    {
        do
        {
            printf("%d ", stack[idx]);
            visit[stack[idx]] = 0;
            idx--;
        } while (x != stack[idx + 1]); //出栈,并且输出。
        printf("\n");
    }
    return;
}
int main()
{
    memset(h, -1, sizeof h);
    int n, m;
    scanf("%d%d", &n, &m);
    int x, y;
    for (int i = 1; i <= m; i++)
    {
        scanf("%d%d", &x, &y);
        add(x, y);
    }
    for (int i = 1; i <= n; i++)
        if (!dfn[i])
            tarjan(i); //当这个点没有访问过,就从此点开始。防止图没走完
    return 0;
}

参考链接

[1]:https://blog.csdn.net/qq_34374664/article/details/77488976 (全网最!详!细!tarjan算法讲解)

[2]:https://baike.baidu.com/item/强连通分量/7448759?fr=aladdin (百度百科-强连通分量)

[3]:https://blog.csdn.net/weixin_43843835/article/details/88381828 (图论——强连通分量(Tarjan算法)

发布了84 篇原创文章 · 获赞 12 · 访问量 2916

猜你喜欢

转载自blog.csdn.net/qq_43294914/article/details/103324836