算法补完计划(四) Tarjan算法

一些概念

割点

无向图中,如果有一个结点x,当我们去掉x结点已经与其相连的边后,该图被分成若干个不相连子图(或者说这个图不联通了),那x即为割点

又叫割边, 如果有一条边edge,我们去掉这条边后,图被分成两个不相连的子图,那这条边就叫做

强连通分量

有向图的极大强连通子图


Tarjan求强连通分量

经典tarjan = _ =,一直学的不大明白,加深一下印象

dfn[x] / low[x]

这俩变量是理解tarjan算法比较重要的东西

dfn[x]是时间戳,这个时间戳的意思就是dfs时访问的顺序

low[x]是x以及x的子树能够追溯到的最早的栈中节点的dfn

(low[]这个概念弄的我一直比较蒙,其实通俗点来说,从x出发,延伸到后面又回到了自己,那肯定是存在一个环的,这个环上的点都是强连通的,add:强连通分量是极大强连通子图

假设从x出发,那我们怎么知道退到x时,它下面的有所有和它构成强连通分量的点呢

开个

:update  5/15 为什么要开栈?

根据这个dfs过程,好像回到打过标记的点就构成强连通分量了,那么为什么还要开栈呢?

随手捏了一个,大概是因为这样,所以要开栈。

并且,在回溯的时候,如果长这样:

我们这里回溯的时候,因为dfn[x] == low[x],所以每个点都是一个强连通,正常弹出。

但如果长这样

我们会在回溯4 3时不执行弹出栈的操作,当回溯到2时,low[2] == dfn[2],这时,弹出2及其顶部的元素3 ,4,而2,3,4构成一个强连通分量,(类似于2是该强连通分量的根。

这下代码就会好理解许多

如果u下面要遍历的点v已经在栈中了,那low[u] = dfn[v]

如果不在栈中,low[u] = min(low[u],low[v] )

dfn[x] = low[x]说明x是强连通分量的根

代码

void tarjan(int u){
    q.push(u);
    in[u] = 1;
    dfn[u] = low[u] = ++Time;//时间戳
    for(int i = head[u]; i; i = edge[i].next){
        int v = edge[i].v;
        if(dfn[v] == 0){
            tarjan(v);//没遍历过就继续
            low[u] = min(low[u], low[v]);//回溯的时候更新最小值
        }
        else if(instack[v] == 1){
            low[u] = min(low[u], dfn[v]);
        }
    }
    if(dfn[u] == low[u]){
        while(!q.empty()){
            int t = q.top();
            q.pop();
            instack[t] = 0;
			fa[t] = u;
			if(t == u)break;
        }
    }
}

这里染了一下色,令强连通分量中的结点fa都为强连通分量的根结点


割点

u不是树根:如果当前结点为u,接下来的结点为v,如果dfn[u] <= low[v],说明了v这个点回溯不到u之前的点,那么去掉u结点,u之前的结点与v将不再联通,u即割点

u是树根:如果fa[u] == u,说明u是根,那就需要特判了,我们记录son[u]的数量,son[u] >= 2,说明u是割点,这里我本来有疑问,如果它的儿子存在联通怎么办 

我们是递归去求的,递归回来之后再去更新信息,如果和上图一样,根结点递归下去之后dfn[左儿子]和dfn[右儿子]已经打上标记了,son[root] == 1


思路其实和割点蛮像的,并且没有什么需要特判的地方

在无向图中,对于边i,其两端点为u,v,如果dfn[u] < low[v],说明边i为桥

这里式子和割点很像,但是不能取等,因为如果dfn[u] == low[v],说明v能回溯到u,该边i就不是v通到u的唯一线路了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值