算法学习:强连通分量 --tarjan

【定义】

【强连通分量】

 在一个子图中,任意点能够直接或者间接到达这个子图中的任意点,这个子图被称为强连通分量

 


【解决问题】

求图的强连通分量

同时能够起到

...................缩点

...................求割点

...................求割边

的效果

 


【算法学习】

首先明确我们的目的,找到强联通分量

所以其实这是一个找环的过程

 

dfn [] :表示 dfs 序

low [] :表示强联通分量里面 dfs 序最小的 dfs 序

 

方法如下:

 按深度优先遍历所有节点

 

遍历当前节点的所有出边

 

  1.如果当前边的中点还没有被访问过,访问

  回溯回来之后比较当前节点的 low 值和终点的 low 值

  将较小的变为当前节点的 low 值

 

  2.如果访问过,则说明我们绕了一个圈

  则比较当前节点的low值和终点的dfn值,将较小的作为当前结点的low值

 

当这个节点的dfn值和low值相等的时候,说明这个点是这个强联通分量的终点

在这个栈上面的点和这个点都在同一个强联通分量里面

 

伪代码如下:

void tarjan(int u)
//当前节点
{
    dfn[u]=low[u]=++cnt;
    //先默认该点是一个强联通分量
    //节点入栈;
    vis[u]=true;//当前节点已入栈
    for(遍历该节点所有出边)
    {
        int v=当前边的终点;
        if (!dfn[v])
      //如果没有被遍历过  
       {
            tarjan(v);//深度优先遍历
            low[u]=min(low[u],low[v]);
        }
        else low[u]=min(dfn[v],low[u]);
    }
    if (low[u]==dfn[u])
    {
        //如果找到了最低的那个节点
        while(栈顶!=v)
        {
            染色;
            出栈;
        }
    }
    染色;
    出栈;
}        

 


【模板】

【luogu 2863】

【题目大意】求图内强联通分量内点个数大于1的强连通分量个数

【题目思路】裸的tarjan求强连通分量

 

再次强调下

分清边的数量和点的数量

分清双向边和单向边

 

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<queue>
#include<stack>
using namespace std;
const int MAXN = 50010;
struct note
{
    int nt;
    int to;
}edge[MAXN];
int top = 0,st[MAXN];
int cnt[MAXN];
int tag[MAXN];
int n, m;
void add(int x, int y)
{
    top++; edge[top] = { st[x],y }, st[x] = top;
}
stack<int> s;
int  dfn[MAXN], low[MAXN],id,col;
bool vis[MAXN];
void tarjan(int now)
{
    dfn[now] = low[now] = ++id;
    vis[now] = true;
    s.push(now);
    for (int i = st[now]; i != -1; i = edge[i].nt)
    {
        int to = edge[i].to;
        if (!dfn[to])
        {
            tarjan(to);
            low[now] = min(low[now], low[to]);
        }
        else
        {
            if (vis[to])
            {
                low[now] = min(low[now], dfn[to]);
            }
        }
    }
    if (dfn[now] == low[now])
    {
        col++; int t;
        do {
            t = s.top();
            s.pop();
            tag[t] = col;
            cnt[col]++;
            vis[t] = false;
        } while (t != now);
    }

}
int main()
{
    scanf("%d%d", &n, &m);
    memset(st, -1, sizeof(st));
    for (int i = 1; i <= m; i++)
    {
        int x, y;
        scanf("%d%d", &x, &y);
        add(x, y);
    }
    int ans = 0;
    for (int i = 1; i <= n; i++)
    {
        if (!tag[i])
            tarjan(i);
    }
    for (int i = 1; i <= col; i++)
        if (cnt[i] > 1)
            ans++;
    printf("%d", ans);
    return 0;
}
View Code

 


 

【扩展】

求割点

 

求割边

 

 


posted @ 2019-08-12 19:52  rentu  阅读(344)  评论(0编辑  收藏  举报