Tarjan 强连通分量 及 双联通分量(求割点,割边)

Tarjan 强连通分量 及 双联通分量(求割点,割边)

众所周知,Tarjan的三大算法分别为

(1)         有向图的强联通分量

(2)         无向图的双联通分量(求割点,桥)

(3)         最近公共祖先

今天主要给未来的自己讲解一下前两个应用,让未来的自己不会向现在的自己一样又忘了Tarjan怎么写。熟悉DFS的话,理解起来会简单很多。

 

(1)         有向图的强联通分量

首先解释Tarjan中几个比较重要的值

DFN[i] : 节点i被访问到的次序

LOW[i]: 节点i的子孙节点能够追溯到的次序最早的祖先节点

Stack[i]: 存储强连通分量

VIS[i]  : VIS[i] = 1 则节点在栈中,否则不在

 

DFN[i] == 0 时,很明显,就是该点没有被访问过

DFN[i]==LOW[i], 切换成中文,意思就是节点i被访问到的次序,是他的子孙节点中能够追溯到的最早次序,换句话说,i和i的子孙节点(并非所有子孙节点,而是所有进栈的子孙节点)构成了一个强连通分量。

 

 

接下来就是重头戏了。让我们开始DFS。

(1) Tarjan开始,对于节点u

有DFN[u] = LOW[u] = ++deep

因为第u个点第一次被访问到的时候还没有访问其子节点

把u加入栈中(将来用于回溯)并且打上VIS标记

 

(2) 对于u的每一条边,所访问到的v节点

如果v节点没有被访问过,那就直接回到第一步

回溯结束后(对于没有子节点的节点,可以见得它的LOW 就等于它的 DFN)

LOW[u] = min(LOW[u],LOW[v])

因为LOW[u] 要取到u的所有子节点中最小的LOW[v]值

 

如果v节点已经在栈中了,

直接LOW[u] = min(LOW[u],LOW[v])

同理,此时已构成环

 

如果v节点被访问到,且v节点不在栈中了

证明v已经出栈,不可与u点构成强联通分量

 

(3) DFN[i]==LOW[i]

当我们回溯到底i个点发现它满足上述条件的话,证明该点和子孙节点能够构成强联通分量。且i是最早入栈的(LOW的定义),这时候只需要退栈到栈顶不是i点就OK了。

 

附上一份代码,

模板Tarjan  POJ 2186

写一次就明白了

const int maxn = 150000;

struct Edge

{

       int from,to,next;

}edge[maxn];

int head[maxn],DFN[maxn],low[maxn];

int Stack[maxn],vis[maxn],color[maxn],deg[maxn];

int deep,top,k,tol;

 

void init()

{

    k = tol = top = deep = 0;

       CLR(head,-1);

       CLR(DFN,0);

       CLR(low,0);

    CLR(color,0);

    CLR(Stack,0);

    CLR(vis,0);

    CLR(deg,0);

}

 

void addedge(int u,int v)

{

    edge[tol].from = u;

       edge[tol].to = v;

       edge[tol].next = head[u];

       head[u] = tol++;

}

 

void tarjan(int u)

{

    DFN[u] = low[u] = ++deep;

    vis[u] = 1;

    Stack[++top] = u;

    for(int i = head[u]; ~i; i = edge[i].next){

        int v = edge[i].to;

        if(!DFN[v]){

            tarjan(v);

            low[u] = min(low[v],low[u]);

        }

        else if(vis[v]){

            low[u] = min(low[v],low[u]);

        }

    }

 

    if(DFN[u] == low[u]){

        color[u] = ++k;

        vis[u] = 0;

        while(Stack[top]!=u){

            color[Stack[top]] = k;

            vis[Stack[top]] = 0;

            top--;

        }

        top--;

    }

}

 

求割边:当LOW[V]>DFN[U]时,证明v点和它的子孙节点无法回溯到u,v间为桥

求割点:当LOW[V]>=DFN[U]时,证明v点和它的子孙节点无法回溯到u的祖先(可以回溯到u).u点为割点

根节点如果有多个子节点,则为割点

 

const int maxn = 1500;
struct Edge
{
	int from,to,next;
	int cut;
}edge[maxn];
int head[maxn],low[maxn],DFN[maxn];
int n,deep,tol,ans;
int cut_point[maxn];

void init()
{
    ans = tol = deep = 0;
	CLR(head,-1);
    CLR(cut_point,0
	CLR(DFN,0);
	CLR(low,0);
}

void addedge(int u,int v)
{
    edge[tol].from = u;
	edge[tol].to = v;
	edge[tol].next = head[u];
	head[u] = tol++;
}

void tarjan(int u, int fa) {    //u在DFS树中的父节点是fa
    low[u] = DFN[u] = ++deep;
    int child = 0;          //子节点数目
    for(int i = head[u]; ~i; i = edge[i].next) {
        int v = edge[i].to;
        if( fa == v ) continue;
        if(!DFN[v]) {
            child++;
            tarjan(v, u);
            low[u] = min(low[u], low[v]);
            if(low[v] >= DFN[u]) {
                if(low[v] > DFN[u]) edge[i].cut = 1;
                cut_point[u] = 1;
            }
        }
        else low[u] = min(low[u], DFN[v]);
    }
    if(fa < 0 && child == 1) cut_point[u] = 0;
}

int search_cut_point()
{
    tarjan(1,-1);
	for(int i=1;i<=n;i++)
        if(cut_point[i])
            ans++;
}

 

转载于:https://www.cnblogs.com/Tokisaki-Kurumi-/p/8613610.html

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值