强连通分量Tarjan

本文深入解析了强连通分量的概念及其实现算法,包括Tarjan算法和Kosaraju算法,通过多个实例展示了如何使用这些算法解决实际问题,如寻找最受欢迎的牛、间谍网络分析等,同时提供了详细的代码实现。

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

参考博客:浅析强连通分量(Tarjan和kosaraju)

                Tarjan·关于图的连通性 & 连通分量

题目:1332 上白泽慧音

代码:

#include<bits/stdc++.h>
using namespace std;

const int maxn = 5010;
const int N =  50010;
stack<int>s;
int n, m, idx = 0, k = 1, Bcnt = 0;
int head[N];
int ins[maxn];
int dfn[maxn], low[maxn];
int belong[maxn];
set<int>g[maxn];
set<int>::iterator it;
int ans = 0;
struct edge
{
    int v, next;
} e[N];
void add(int u, int v)
{
    e[k].v = v;
    e[k].next = head[u];
    head[u] = k++;
}
void tarjan(int u)
{
    int v;
    dfn[u] = low[u] = ++idx;
    ins[u] = 1;
    s.push(u);
    for(int i = head[u]; i != -1; i = e[i].next)
    {
        v = e[i].v;
        if(!dfn[v])
        {
            tarjan(v);
            low[u] = min(low[u], low[v]);
        }
        else if(ins[v])low[u] = min(low[u], dfn[v]);
    }
    if(dfn[u] == low[u])
    {
        Bcnt++;
        do
        {
            v = s.top();
            s.pop();
            ins[v] = 0;
            g[Bcnt].insert(v);
        }
        while(u != v);
        int  f = g[Bcnt].size();
        ans = max(f, ans);
    }
}
void init()
{
    idx = 0, k = 1, Bcnt = 0, ans = 0;
    memset(ins, 0, sizeof(ins));
    memset(dfn, 0, sizeof(dfn));
    memset(low, 0,sizeof(low));
    memset(head, -1, sizeof(head));
}
int main()
{
    int T, p, u, v, choose;
    init();
    scanf("%d %d", &n, &p);
    for(int i = 1; i <= p; i++)
    {
        scanf("%d %d %d", &u, &v, &choose);
        add(u, v);
        if(choose == 2)add(v, u);
    }
    for(int i= 1; i <= n; i++)if(!dfn[i])tarjan(i);
    int mi = INT_MAX, pos = 0;
    cout<<ans<<endl;
    for(int i = 1; i <= Bcnt; i++)
    {
        if(g[i].size() == ans)
        {
            int first_num = *(g[i].begin());
            if(mi > first_num)
            {
                pos = i;
                mi = first_num;
            }
        }
    }
    int t = 0;
    for(it = g[pos].begin(); it != g[pos].end(); it++)
    {
        if(t != 0)cout<<" ";
        printf("%d", *it);
        t++;
    }
    cout<<endl;
    return 0;
}

题目:Popular Cows    POJ - 2186 

题意:就是有N头牛,给你M中关系,问你有多少头牛受欢迎,当一个牛被除了自己本身的所有牛喜欢那么这头牛就是最受欢迎的牛。

题解:本题采用Tarjan+缩点,把强连通分量看成一个点,统计出度为0的点有多少个,如果出度为0的点为一个的话,那么则这个点(可能是强连通分量缩的点)就是最受欢的,输出这个点包含多少头牛。如果出度为0的点有两个以上,那么就不存在最受欢迎的牛。

代码:

#include<stdio.h>
#include<iostream>
#include<stack>
#include<string.h>
#include<algorithm>
using namespace std;
const int maxn = 10010;
const int N = 50010;
stack<int>s;
int n, m, idx = 0, k = 1, Bcnt = 0;
int head[N];
int ins[maxn];
int dfn[maxn], low[maxn];
int belong[maxn];
int num[maxn];
int degree[maxn];

struct edge
{
    int v, next;
} e[N];
void add(int u, int v)
{
    e[k].v = v;
    e[k].next = head[u];
    head[u] = k++;
}
void tarjan(int u)
{
    int v;
    dfn[u] = low[u] = ++idx;
    ins[u] = 1;
    s.push(u);
    for(int i = head[u]; i != -1; i = e[i].next)
    {
        v = e[i].v;
        if(!dfn[v])
        {
            tarjan(v);
            low[u] = min(low[u], low[v]);
        }
        else if(ins[v])low[u] = min(low[u], dfn[v]);
    }
    if(dfn[u] == low[u])
    {
        Bcnt++;
        do
        {
            v = s.top();
            s.pop();
            ins[v] = 0;
            belong[v] = Bcnt;
            num[Bcnt]++;

        }
        while(u != v);
    }
}
void init()
{
    idx = 0, k = 1, Bcnt = 0;
    memset(ins, 0, sizeof(ins));
    memset(dfn, 0, sizeof(dfn));
    memset(low, 0,sizeof(low));
    memset(head, -1, sizeof(head));
    memset(num, 0, sizeof(num));
    memset(belong , 0, sizeof(belong));
    memset(degree, 0, sizeof(degree));
}
int main()
{
    int T, p, u, v, choose;
    init();
    scanf("%d %d", &n, &p);
    for(int i = 1; i <= p; i++)
    {
        scanf("%d %d", &u, &v);
        add(u, v);
    }
    for(int i= 1; i <= n; i++)if(!dfn[i])tarjan(i);
    for(int i = 1; i <= n; i++){
        for(int j = head[i]; j != -1; j = e[j].next){
            int v = e[j].v;
            if(belong[i] != belong[v]){
                degree[belong[i]]++;
            }
        }
    }
    int flag = 0, pos = 0;
    for(int i = 1; i <= Bcnt; i++){
        if(degree[i] == 0){
                flag++;
                pos = i;
        }
    }
    if(flag > 1)printf("0\n");
    else printf("%d\n", num[pos]);
    return 0;
}


题目:P2863 [USACO06JAN]牛的舞会The Cow Prom

代码:

#include<stdio.h>
#include<iostream>
#include<stack>
#include<string.h>
#include<algorithm>
using namespace std;
const int maxn = 10010;
const int N = 50010;
stack<int>s;
int n, m, idx = 0, k = 1, Bcnt = 0;
int head[N];
int ins[maxn];
int dfn[maxn], low[maxn];
int belong[maxn];
int num[maxn];

struct edge
{
    int v, next;
} e[N];
void add(int u, int v)
{
    e[k].v = v;
    e[k].next = head[u];
    head[u] = k++;
}
void tarjan(int u)
{
    int v;
    dfn[u] = low[u] = ++idx;
    ins[u] = 1;
    s.push(u);
    for(int i = head[u]; i != -1; i = e[i].next)
    {
        v = e[i].v;
        if(!dfn[v])
        {
            tarjan(v);
            low[u] = min(low[u], low[v]);
        }
        else if(ins[v])low[u] = min(low[u], dfn[v]);
    }
    if(dfn[u] == low[u])
    {
        Bcnt++;
        do
        {
            v = s.top();
            s.pop();
            ins[v] = 0;
            belong[v] = Bcnt;
            num[Bcnt]++;

        }
        while(u != v);
    }
}
void init()
{
    idx = 0, k = 1, Bcnt = 0;
    memset(ins, 0, sizeof(ins));
    memset(dfn, 0, sizeof(dfn));
    memset(low, 0,sizeof(low));
    memset(head, -1, sizeof(head));
    memset(num, 0, sizeof(num));
    memset(belong, 0, sizeof(belong));
}
int main()
{
    int T, p, u, v, choose;
    init();
    scanf("%d %d", &n, &p);
    for(int i = 1; i <= p; i++)
    {
        scanf("%d %d", &u, &v);
        add(u, v);
    }
    for(int i= 1; i <= n; i++)if(!dfn[i])tarjan(i);
    int ans = 0;
    for(int i = 1; i <= Bcnt; i++)
    {
        if(num[i] > 1)ans++;
    }
    printf("%d\n", ans);
    return 0;
}


缩点:

题目:P3387 【模板】缩点

代码:

#include<stdio.h>
#include<iostream>
#include<stack>
#include<string.h>
#include<algorithm>
#include<queue>
using namespace std;
const int maxn = 10010;
const int N = 500100;
stack<int>s;
int n, m, idx = 0, k = 1, Bcnt = 0, cn = 0, ans;
int head[N], head1[N];
int ins[maxn];
int dfn[maxn], low[maxn];
int belong[maxn], point[maxn], w[maxn], degree[maxn];
int dis[maxn];

struct edge
{
    int v, next;
} e[N];
struct edge_
{
    int v, next;
} e_[N];
void add(int u, int v)
{
    e[k].v = v;
    e[k].next = head[u];
    head[u] = k++;
}
void add_(int u, int v)
{
    e_[cn].v = v;
    e_[cn].next = head1[u];
    head1[u] = cn++;
}
void tarjan(int u)//强连通分量
{
    int v;
    dfn[u] = low[u] = ++idx;
    ins[u] = 1;
    s.push(u);
    for(int i = head[u]; i != -1; i = e[i].next)
    {
        v = e[i].v;
        if(!dfn[v])
        {
            tarjan(v);
            low[u] = min(low[u], low[v]);
        }
        else if(ins[v])low[u] = min(low[u], dfn[v]);
    }
    if(dfn[u] == low[u])
    {
        Bcnt++;
        do
        {
            v = s.top();
            s.pop();
            ins[v] = 0;
            belong[v] = Bcnt;
        }
        while(u != v);
    }
}
void init()
{
    idx = 0, k = 1, Bcnt = 0, cn = 1, ans = -1;
    memset(head1, -1,sizeof(head1));
    memset(head, -1, sizeof(head));

}
void TopSort()//拓扑排序
{
     queue<int>q;
     for(int i = 1; i <= Bcnt; i++){
        if(!degree[i])q.push(i);
        dis[i] = w[i];
     }
     while(!q.empty()){
        int x = q.front();
        q.pop();
        for(int i = head1[x]; i != -1; i = e_[i].next){
            int v = e_[i].v;
            dis[v] = max(dis[v], dis[x]+w[v]);
            degree[v]--;
            if(degree[v] == 0)q.push(v);
        }
     }
     for(int i = 1; i <= Bcnt; i++){
        ans = max(ans, dis[i]);
     }


}
int main()
{
    int T, p, u, v, choose;
    init();
    scanf("%d %d", &n, &p);
    for(int i = 1; i <= n; i++)
    {
        scanf("%d", &point[i]);
    }
    for(int i = 1; i <= p; i++)
    {
        scanf("%d %d", &u, &v);
        add(u, v);
    }
    for(int i= 1; i <= n; i++)if(!dfn[i])tarjan(i);
    for(int i = 1; i <= n; i++)
    {
        int v;
        for(int j = head[i]; j != -1; j = e[j].next)
        {
            v = e[j].v;
            if(belong[i] != belong[v])//缩点
            {
                add_(belong[i], belong[v]);
                degree[belong[v]]++;
            }
        }
        w[belong[i]] += point[i];
    }
    TopSort();
    printf("%d\n", ans);
    return 0;
}


题目:P1262 间谍网络

思路:本题的思路就是,如果当这个人不能被贿赂并且这个人跟其他人没有关系,那么就是NO。剩下的就是利用缩点,把环缩成一个点,那么这个点的最小值就是整个点的价值。

代码:

#include<stdio.h>
#include<iostream>
#include<stack>
#include<string.h>
#include<algorithm>
using namespace std;
const int maxn = 10010;
const int N = 50010;
const int INF = 1e9+100;
stack<int>s;
int n, m, idx = 0, k = 1, Bcnt = 0;
int head[N];
int ins[maxn];
int dfn[maxn], low[maxn];
int belong[maxn];
int num[maxn];
int degree[maxn], money[maxn];

struct edge
{
    int v, next;
} e[N];
void add(int u, int v)
{
    e[k].v = v;
    e[k].next = head[u];
    head[u] = k++;
}
void tarjan(int u)
{
    int v;
    dfn[u] = low[u] = ++idx;
    ins[u] = 1;
    s.push(u);
    for(int i = head[u]; i != -1; i = e[i].next)
    {
        v = e[i].v;
        if(!dfn[v])
        {
            tarjan(v);
            low[u] = min(low[u], low[v]);
        }
        else if(ins[v])low[u] = min(low[u], dfn[v]);
    }
    if(dfn[u] == low[u])
    {
        Bcnt++;
        do
        {
            v = s.top();
            s.pop();
            ins[v] = 0;
            belong[v] = Bcnt;
            num[Bcnt] = (num[Bcnt], money[v]);//更新环的最小值

        }
        while(u != v);
    }
}
void init()
{
    idx = 0, k = 1, Bcnt = 0;
    memset(ins, 0, sizeof(ins));
    memset(dfn, 0, sizeof(dfn));
    memset(low, 0,sizeof(low));
    memset(head, -1, sizeof(head));
    memset(num, 0, sizeof(num));
    memset(belong, 0, sizeof(belong));
}
int main()
{
    int T, p, u, v, choose;
    init();
    scanf("%d %d", &n, &p);
    for(int i = 1; i <= n; i++)money[i] = INF;
    for(int i = 1; i <= p; i++)
    {
        int pos, val;
        scanf("%d %d", &pos, &val);
        money[pos] = val;
    }
    int m;
    scanf("%d", &m);
    for(int i = 1; i <= m; i++)
    {
        scanf("%d %d", &u, &v);
        add(u, v);
    }
    for(int i= 1; i <= n; i++)if(!dfn[i] && money[i] != INF)tarjan(i);
    for(int i = 1; i <= n; i++)
    {
        if(!dfn[i]) //如果这个人没有被贿赂,或者跟其他人没有关系
        {
            printf("NO\n");
            printf("%d\n", i);
            return 0;
        }
    }
    int ans = 0;
    for(int i = 1; i <= n; i++)
    {
        for(int j = head[i]; j != -1; j = e[j].next)
        {
            int v = e[j].v;
            if(belong[i] != belong[v])
            {
                degree[belong[v]]++;
            }
        }
    }
    for(int i = 1; i <= Bcnt; i++)
    {
        if(!degree[i]) //利用缩点,找到入度为0的点,
        {
            ans += num[i];
        }
    }
    printf("YES\n");
    printf("%d\n", ans);
    return 0;
}



割点:

题目:P3388 【模板】割点(割顶)

代码:

#include<bits/stdc++.h>
using namespace std;
const int maxn = 20010;
const int N = 200100;
int head[N];
int dfn[maxn], low[maxn], k = 0, tot = 0, root, cut[maxn];
struct Node
{
    int v, next;
} e[N];
void add(int u, int v)
{
    e[k].v = v;
    e[k].next = head[u];
    head[u] = k++;
}
void Tarjan(int x)
{
    dfn[x] = low[x] = ++tot;
    int flag = 0;
    for(int i = head[x]; i != -1; i = e[i].next)
    {
        int y = e[i].v;
        if(!dfn[y])
        {
            Tarjan(y);
            low[x] = min(low[x], low[y]);
            if(dfn[x] <= low[y] && (x != root || ++flag > 1))cut[x] = 1;
        }
        else low[x] = min(low[x], dfn[y]);
    }
}
int main()
{
    memset(head, -1, sizeof(head));
    int n, m, a, b;
    scanf("%d %d", &n, &m);
    for(int i = 1; i <= m; i++)
    {
        scanf("%d %d", &a, &b);
        add(a, b);
        add(b, a);
    }
    for(int i = 1; i <= n; i++)
    {
        if(!dfn[i])root = i, Tarjan(i);
    }
    int cn = 0;
    for(int i = 1; i <= n; i++)
    {
        if(cut[i])cn++;
    }
    printf("%d\n", cn);
    for(int i =  1; i <= n; i++)
    {
        if(cut[i])printf("%d ", i);
    }
    printf("\n");
}

题目:Network of Schools    POJ - 1236 

思路:第一问就是问你最少给多少人发信息,所以利用缩点,然后寻找入度为0的点就是第一问的答案。第二问,最少增加多少条边,这个图示强联通图,所以就是缩点之后,找到入度和初度为0的最大值。

代码:

#include<stdio.h>
#include<iostream>
#include<stack>
#include<string.h>
#include<algorithm>
#include<queue>
using namespace std;
const int maxn = 10010;
const int N = 500100;
stack<int>s;
int n, m, idx = 0, k = 1, Bcnt = 0, cn = 0, ans;
int head[N], head1[N];
int ins[maxn];
int dfn[maxn], low[maxn];
int belong[maxn], in[maxn], out[maxn];

struct edge
{
    int v, next;
} e[N];
struct edge_
{
    int v, next;
} e_[N];
void add(int u, int v)
{
    e[k].v = v;
    e[k].next = head[u];
    head[u] = k++;
}
void add_(int u, int v)
{
    e_[cn].v = v;
    e_[cn].next = head1[u];
    head1[u] = cn++;
}
void tarjan(int u)//强连通分量
{
    int v;
    dfn[u] = low[u] = ++idx;
    ins[u] = 1;
    s.push(u);
    for(int i = head[u]; i != -1; i = e[i].next)
    {
        v = e[i].v;
        if(!dfn[v])
        {
            tarjan(v);
            low[u] = min(low[u], low[v]);
        }
        else if(ins[v])low[u] = min(low[u], dfn[v]);
    }
    if(dfn[u] == low[u])
    {
        Bcnt++;
        do
        {
            v = s.top();
            s.pop();
            ins[v] = 0;
            belong[v] = Bcnt;
        }
        while(u != v);
    }
}
void init()
{
    idx = 0, k = 1, Bcnt = 0, cn = 1, ans = -1;
    memset(head1, -1,sizeof(head1));
    memset(head, -1, sizeof(head));

}

int main()
{
    int T, p, u, v, choose;
    init();
    int n;
    scanf("%d", &n);
    for(int i = 1; i <= n; i++)
    {
        int v;
        while(scanf("%d", &v) && v != 0)
        {
            add(i, v);
        }
    }
    for(int i= 1; i <= n; i++)if(!dfn[i])tarjan(i);
    for(int i = 1; i <= n; i++)
    {
        int v;
        for(int j = head[i]; j != -1; j = e[j].next)
        {
            v = e[j].v;
            if(belong[i] != belong[v])//缩点
            {
                add_(belong[i], belong[v]);
                in[belong[v]]++;
                out[belong[i]]++;
            }
        }
    }
    int ans1 = 0, ans2 = 0;
    for(int i = 1; i <= Bcnt; i++)
    {
        if(!in[i])ans1++;
        if(!out[i])ans2++;
    }
    printf("%d\n", ans1);
    if(Bcnt ==  1)
    {
        printf("0\n");
        return 0;
    }


    ans = max(ans1, ans2);
    printf("%d\n", ans);
    return 0;
}

 

<think>好的,我现在需要给用户介绍C++中的Tarjan强连通分量算法。首先,我得回忆一下Tarjan算法的基本概念和步骤,确保自己理解正确。 Tarjan算法是由Robert Tarjan提出的,用于寻找有向图中的强连通分量(SCC)。强连通分量指的是图中最大的子图,其中任意两个顶点都可以互相到达。这个算法使用深度优先搜索(DFS)来遍历图,并通过维护一个栈和一些辅助数组来跟踪节点的访问状态。 接下来,我应该分步骤详细说明算法的实现过程。首先,每个节点都有一个索引(index)和低链接值(lowlink)。索引表示节点被访问的顺序,低链接值是该节点能回溯到的最早访问的节点的索引。当某个节点的低链接值等于其索引时,说明找到了一个强连通分量,需要将栈中的节点弹出,直到当前节点为止,这些节点组成一个SCC。 然后,我需要考虑如何在C++中实现这个算法。通常会用邻接表来表示图结构,使用栈来保存当前路径上的节点,以及两个数组来存储索引和低链接值。还需要一个布尔数组来记录节点是否在栈中,以避免重复处理。 可能会遇到的问题包括如何初始化这些数组,如何处理未访问的节点,以及在DFS过程中如何更新低链接值。例如,当访问一个已经在栈中的节点时,需要比较当前节点的低链接值和目标节点的索引,取较小值来更新当前的低链接值。 此外,需要注意递归DFS的实现细节,比如递归终止条件,以及何时将节点弹出栈并记录SCC。代码示例需要清晰,变量命名要合理,确保用户能够理解每一步的作用。 最后,我应该测试一下这个算法,比如使用一个简单的有向图来验证输出是否正确。例如,对于包含四个节点的图,边为0→1,1→2,2→0,1→3,算法应该输出两个SCC:{0,1,2}和{3}。 在编写代码示例时,要注意注释和格式,帮助用户理解关键步骤。同时,要提醒用户关于时间复杂度的部分,Tarjan算法的时间复杂度是O(V+E),适合处理较大的图结构。 总结下来,我需要结构清晰地介绍算法原理、步骤,并提供可运行的C++代码示例,确保用户能够理解并实现该算法。同时,要检查是否有遗漏的关键点,比如栈的使用和低链接值的更新逻辑,确保解释准确无误。</think>以下是关于Tarjan强连通分量算法的逐步说明和C++实现: **算法核心思想** 1. 通过DFS遍历图时维护一个栈 2. 使用两个关键数组: - $index[]$ 记录访问顺序 - $lowLink[]$ 记录能回溯到的最早节点 3. 当$lowLink[u] == index[u]$时,栈顶到当前节点的元素构成一个SCC **算法步骤** 1. 初始化全局计数器和标记数组 2. 对未访问的节点进行DFS 3. 每次DFS时: - 设置当前节点的index和lowLink - 将节点压入栈并标记在栈中 4. 遍历邻接节点: - 如果未访问,递归访问并更新lowLink - 如果已在栈中,更新lowLink值 5. 发现根节点时弹出栈中元素构成SCC **C++实现代码** ```cpp #include <vector> #include <stack> #include <algorithm> using namespace std; vector<vector<int>> sccTarjan(vector<vector<int>>& graph) { int n = graph.size(), index = 0; vector<int> indexMap(n, -1), lowLink(n, -1); vector<bool> onStack(n, false); stack<int> st; vector<vector<int>> scc; auto dfs = [&](auto self, int u) -> void { indexMap[u] = lowLink[u] = index++; st.push(u); onStack[u] = true; for(int v : graph[u]) { if(indexMap[v] == -1) { // 未访问 self(self, v); lowLink[u] = min(lowLink[u], lowLink[v]); } else if(onStack[v]) { // 已在栈中 lowLink[u] = min(lowLink[u], indexMap[v]); } } if(lowLink[u] == indexMap[u]) { // 发现SCC vector<int> component; while(true) { int v = st.top(); st.pop(); onStack[v] = false; component.push_back(v); if(v == u) break; } scc.push_back(component); } }; for(int i = 0; i < n; ++i) { if(indexMap[i] == -1) { dfs(dfs, i); } } return scc; } ``` **算法特征** - 时间复杂度:$O(V + E)$(线性时间) - 空间复杂度:$O(V)$(栈和辅助数组) - 特点:单次DFS即可找出所有SCC **使用示例** ```cpp // 构造图:0→1→2→0,1→3 vector<vector<int>> graph = { {1}, // 0 {2,3}, // 1 {0}, // 2 {} // 3 }; auto components = sccTarjan(graph); // 输出结果:[[3], [0,1,2]] ``` **应用场景** 1. 编译器中的代码优化 2. 电子电路分析 3. 社交网络社群发现 4. 代码依赖分析 5. 强连通性检测 关键理解点:$lowLink[u]$的更新策略既包含前向边的更新(递归返回时),也包含横向边的更新(遇到栈中节点时),这保证了能正确识别环路结构。当某个节点的lowLink等于其index值时,说明找到了环路的根节点。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值