求强连通分量几种算法的比较

本文详细分析了三种求解有向图强连通分量的算法:Kosaraju、Tarjan和Garbow。这三种算法中,Tarjan和Garbow的思想相似,主要利用DFS搜索和时间戳。Kosaraju算法则利用原图和反图的关系,通过两次DFS遍历。三种算法的时间复杂度均为O(n+m),但常数项Garbow最小,实现上也更为巧妙。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

对于求有向图的强连通分量 , 我常用的一般只有这三种算法Kosaraju、Tarjan、Garbow,而这三种算法 , Tarjan和Garbow算法的思想是一样的 , 只不过是实现的方法不一样 , 下面就逐一分析这三个算法:

1、Kosaraju
   Kosaraju算法主要是利用有向图中原图和其反图之间的关系 , 对于一个原图中的强连通分量 , 其在反图中还是强连通分量 , 原图中存在u 到 v的边 , 在反图中就只存在 v 到 u的边 , 而不存在u 到 v的边。 根据这个关系 , 我们先用原图进行dfs搜索 , 得到了一个森林(树) , 我们再按照原图中得到的dfs搜索序列 , 在其反图中进行dfs搜索 , 每次搜索能遍历到的顶点就是一个强连通分量。

代码:
//Kosaraju算法

int dfsone(int cur)
{
    temp[cur] = true;
    for(int i = head[cur] ; i != -1; i = edge[i].next)
    {
        if(!temp[edge[i].t])  dfsone(edge[i].t);
    }
    num[++sig] = cur;
    return 0;
}

int dfstwo(int cur , int sig)
{
    temp[cur] = true;
    scc[cur] = sig;
    for(int i = head2[cur] ; i != -1; i = edge[i].next2)
    {
        if(!temp(edge[i].f))
            dfstwo(edge[i].f , sig);
    }
    return 0;
}

int Kosaraju()
{
    int sig = 0;
    memset(temp , 0 , sizeof(temp));
    for(int i = 1; i <=n ; i++)
    {
        if(!temp[i])
            dfsone(i , sig);
    }

    sig = 0;
    memset(temp , 0 , sizeof(temp));

    for(int i = n; i > 0; i--)
    {
        if(!temp[num[i]])
        {
            dfstwo(num[i] , sig++);
        }
    }

    return sig;
}

2、Tarjan算法
   Tarjan是利用跟求割点和桥的算法的思想差不多 , 都是利用dfs + 时间戳 。
   如果对原图进行dfs搜索 , 那么我们有强连通分量的定义可知 , 任何一个强连通分量是原图的dfs搜索的子树 , 那么我们只要确定了强连通分量在dfs序列中的根 , 我们就能确定这个强连通分量 。 在进行dfs时 , 我们用两个数组tem 、 low来记录每个点的时间戳 , low记录每个点的访问时间 , tem记录所有子孙的最小low值 ,tem的初始值和low一样 。 所以在dfs搜索的回溯过程中 , 如果low[v] == tem[v] , 我们我们就能确定一个强连通分量。

代码:
//tarjan算法
void dfs(int u)
{
    node.push(u);
    low[u] = tem[u] = ++dfs_clock;
    int i;
    for(i = head[i] ; i != -1; i = edge[i].next)
    {
        v = edge[i].to;
        if(!pre[v])
        {
            pre[v] = 1;
            int lowv = = dfs(v);
            tem[u] = min(lowv , tem[u]);
        }
        else if(!sccno[i])  tem[u] = min(tem[u] , tem[v]);//tem是记录每个点的子孙所能到达的low最小的点
    }

    if(low[u] == tem[u])
    {
        scc_clock++;
        do
        {
            int x = node.top(); node.pop();
            sccno[x] = scc_clock;
        }while(x != u)
    }
}

3、Garbow算法
   Garbow算法和Tarjan算法的另一种实现Tarjan算法是用了tem数组来求强连通分量的根 , 而Carbow算法是利用一个stack来求强连通分量的根。
代码:
//carbow算法

void dfs(int u)
{
    node1.push(u) , node2.push(u);
    low[u] = ++dfs_clock;
    int i , v;
    for(i = head[i] ; i != -1; i = edge[i].next)
    {
        v = edge[i].to;
        if(!pre[v])
        {
            pre[v] = 1;
            dfs(v);
        }
        else if(!sccno[v]) //这里就表示已经存在一个环了 , node2就把这个环中除点v之外所有点都删除
        {
            while(low[node2.top()] > low[v])
                node2.pop();
        }
    }

    if(node2.top() == u)
    {
        scc_clock++;
        node2.pop();
        do
        {
            x = node1.top();
            node1.pop();
            sccno[x] = scc_clock;
        }while(x != u)
    }
}

三种算法的比较:
这三种算法由于都是用的邻接表 , 因此时间复杂度都为:O(n+m) , 但是Carbow算法的常数最小 , Tarjan次之 , 并且Carbow算法的是实现非常精妙。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值