Tarjan算法与有向图连通性(强连通分量)

搜索树:

给定有向图 G = ( V , E ) G=(V,E) G=(V,E),若存在 r ∈ V r\in V rV,满足从 r r r出发能够到达 V V V中所有的点,则称 G G G是一个"流图",记为 ( G , r ) (G,r) (G,r),其中 r r r称为流图的源点。在一个流图 ( G , r ) (G,r) (G,r)上从 r r r出发进行深度优先遍历,每个点只访问一次。所有发生递归的边 ( x , y ) (x,y) (x,y)(也即从 x x x y y y是对 y y y的第一次访问)构成一棵以 r r r为根的树,我们把它称为流图 ( G , r ) (G,r) (G,r)的搜索树。

时间戳:

d f s dfs dfs中每个节点第一次被访问的时间顺序。也就是 d f s dfs dfs序。

流图的有向边:

每条有向边 ( x , y ) (x,y) (x,y)必然是以下四种之一:
1.树枝边,指搜索树中的边,即 x x x y y y的父节点。
2.前向边,指搜索树中 x x x y y y的祖先节点。
3.后向边,指搜索树中 y y y x x x的祖先节点。
4.横叉边,指除了以上三种情况之外的边,它一定满足 d f n [ y ] < d f n [ x ] dfn[y]<dfn[x] dfn[y]<dfn[x]

有向图的强连通分量:

给定一张有向图。若对于图中任意两个节点 x , y x,y x,y既存在从 x x x y y y的路径,也存在从 y y y x x x的路径,则称该有向图是"强连通图"。
有向图的极大连通子图被称为"强连通分量",简记为 S C C SCC SCC
一个环一定是强连通图,因此 t a r j a n tarjan tarjan算法的基本思路就是对于每一个点,尽量找到与它一起能构成环的所有节点。

追溯值:

后向边 ( x , y ) (x,y) (x,y)非常有用,因为它可以和搜索树上从 y y y x x x的路径一起构成环。横叉边 ( x , y ) (x,y) (x,y)视情况而定,如果从 y y y出发能找到一条路径回到 x x x的祖先节点,那么 ( x , y ) (x,y) (x,y)就是有用的。为了找到环,引入了追溯值的概念。
s u b t r e e ( x ) subtree(x) subtree(x)表示流图的搜索树中以 x x x为根的子树。 x x x的追溯值 l o w [ x ] low[x] low[x]定义为满足以下条件的节点的最小时间戳:
1.改点在栈中。
2.存在一条从 s u b t r e e ( x ) subtree(x) subtree(x)出发的有向边,以该点为终点。
t a r j a n tarjan tarjan按照以下步骤计算追溯值:
1.当节点 x x x第一次被访问时,把 x x x入栈,初始化 l o w [ x ] = d f n [ x ] low[x]=dfn[x] low[x]=dfn[x]
2.扫描从 x x x出发的每条边 ( x , y ) (x,y) (x,y)
(1)若 y y y没被访问过,说明 ( x , y ) (x,y) (x,y)是树枝边,递归访问 y y y,从 y y y回溯之后,令 l o w [ x ] = m i n ( l o w [ x ] , l o w [ y ] ) low[x]=min(low[x],low[y]) low[x]=min(low[x],low[y])
(2)若 y y y被访问过并且 y y y在栈中,则令 l o w [ x ] = m i n ( l o w [ x ] , d f n [ y ] ) low[x]=min(low[x],dfn[y]) low[x]=min(low[x],dfn[y])
3.从 x x x回溯之前,判断是否有 l o w [ x ] = d f n [ x ] low[x]=dfn[x] low[x]=dfn[x]。若成立,则不断从栈中弹出节点,直至 x x x出栈。

强连通分量的判定法则:

x x x回溯之前,若有 l o w [ x ] = d f n [ x ] low[x]=dfn[x] low[x]=dfn[x]成立,则栈中从 x x x到栈顶的所有节点构成一个强连通分量。因为 l o w [ x ] = d f n [ x ] low[x]=dfn[x] low[x]=dfn[x]说明 s u b t r e e ( x ) subtree(x) subtree(x)中的节点不能与栈中其他节点一起构成环。另外因为横叉边的终点时间戳必然小于起点时间戳,所以 s u b t r e e ( x ) subtree(x) subtree(x)中的节点也不可能直接到达尚未访问的节点。因此栈中从 x x x到栈顶的所有节点不可能与其他节点一起构成环。
c o d e : code: code

#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
using namespace std;

const int maxn=1e5+5;
const int maxm=1e5+5;

struct edge
{
    int to,nxt;
}Edge[maxm];

int head[maxn],dfn[maxn],low[maxn],Stack[maxn],ins[maxn],id[maxn];
vector<int> scc[maxn];
int n,m,tot,num,top,cnt;

void addedge(int x,int y)
{
    Edge[++tot].to=y,Edge[tot].nxt=head[x],head[x]=tot;
}

void tarjan(int x)
{
    dfn[x]=low[x]=++num;
    Stack[++top]=x,ins[x]=1;
    int y;
    for(int i=head[x];i;i=Edge[i].nxt)
    {
        y=Edge[i].to;
        if(!dfn[y])
        {
            tarjan(y);
            low[x]=min(low[x],low[y]);
        }
        else if(ins[y])
            low[x]=min(low[x],dfn[y]);
    }
    if(dfn[x]==low[x])
    {
        ++cnt;
        do
        {
            y=Stack[top--],ins[y]=0;
            id[y]=cnt,scc[cnt].push_back(y);
        }while(x!=y);
    }
}

int main()
{
    scanf("%d%d",&n,&m);
    int x,y;
    for(int i=0;i<m;i++)
    {
        scanf("%d%d",&x,&y);
        addedge(x,y);
    }
    for(int i=1;i<=n;i++)
        if(!dfn[i])
            tarjan(i);
    return 0;
}

缩点:

把每一个 S C C SCC SCC缩成一个点。
c o d e : code: code

#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
using namespace std;

const int maxn=1e5+5;
const int maxm=1e5+5;

struct edge
{
    int to,nxt;
}Edge[maxm];

int head_sd[maxn];
edge sd[maxm];
int head[maxn],dfn[maxn],low[maxn],Stack[maxn],ins[maxn],id[maxn];
vector<int> scc[maxn];
int n,m,tot,num,top,cnt,tol;

void addedge(int x,int y)
{
    Edge[++tot].to=y,Edge[tot].nxt=head[x],head[x]=tot;
}

void addedge_sd(int x,int y)
{
    sd[++tol].to=y,sd[tol].nxt=head_sd[x],head_sd[x]=tol;
}
void tarjan(int x)
{
    dfn[x]=low[x]=++num;
    Stack[++top]=x,ins[x]=1;
    int y;
    for(int i=head[x];i;i=Edge[i].nxt)
    {
        y=Edge[i].to;
        if(!dfn[y])
        {
            tarjan(y);
            low[x]=min(low[x],low[y]);
        }
        else if(ins[y])
            low[x]=min(low[x],dfn[y]);
    }
    if(dfn[x]==low[x])
    {
        ++cnt;
        do
        {
            y=Stack[top--],ins[y]=0;
            id[y]=cnt,scc[cnt].push_back(y);
        }while(x!=y);
    }
}

int main()
{
    scanf("%d%d",&n,&m);
    int x,y;
    for(int i=0;i<m;i++)
    {
        scanf("%d%d",&x,&y);
        addedge(x,y);
    }
    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;j=Edge[j].nxt)
        {
            y=Edge[j].to;
            if(id[i]==id[y])
                continue;
            addedge_sd(id[i],id[y]);
        }
    }
    return 0;
}

结论:
1.缩点后得到的一定是一个有向无环图 ( D A G ) (DAG) (DAG)
2.从 D A G DAG DAG中所有入度为 0 0 0的点出发一定可以遍历整张图。
3. D A G DAG DAG中至少存在 1 1 1个出度为 0 0 0的点。
4.当 D A G DAG DAG中只有 1 1 1个出度为 0 0 0的点时,其他所有点均可到达该点。
5.任意一个有向图,在缩点后的有向无环图中有 p p p个零入度点, q q q个零出度点,那么最少添加 m a x ( p , q ) max(p,q) max(p,q)条边就可以把这个图变成强连通图。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值