对Tarjan求割点的理解

Tarjan算法求割点

模板题:P3388

求割点要注意的是与求强连通分量的区别:
1、割点对于的是无向图,而非有向图。
2、不需要栈数组
3、low[]的含义变化为:最早能绕到的祖先节点(这里的定义是洛谷题解里面觉得最透彻的,而我觉得应该是:不通过父亲节点,最早能绕到的祖先节点。 下面会有解释~ ),为什么是说绕到,因为是有向图,所以一定会有 儿子–>父亲 这条路,而不能通过这条路到它们的祖先节点(这是因为如果算上这条路的话,儿子 通过这条路,肯定能到达祖先节点,那么low[儿子]的值可能会小于low[父亲],导致low[]数组意义上就是说:如果去掉父亲节点后, 儿子 可以达到 祖先节点,而实际上如果去掉父亲节点,儿子–>父亲 这条路就没有了,儿子是无法到达祖先节点的,所以不能算上这条边!
4、对于已访问过的节点,必须是low[u]=min(low[u],dfn[v]),而不能是low[u]=min(low[u],low[v])。(后面有细说)

5、判断割点:
①、如果不是根节点,判断它的儿子节点 v ,是否有满足 low[v]>=dfn[u],如果满足,则说明 v 无法绕到 u 的上面节点,而如果去掉 u ,则 v 无法绕到 u 上面节点也就是 v 的祖先节点。
②、对于是根节点,很显然判断这个根节是否有两个子树(子树通过回溯判断,如果有一个子树,则满足这个子树中肯定有一个节点作为子根,然后其他节点连接这个子根节点,之后这个子根再连接根节点)。
对于上面第3、4点,这里提供一个数据:
输入第一行为 n(点数),m(无向边)。
此后则有 m 条无向边。
5 6

1 2

2 3

3 4

4 5

1 3

3 5

我们模拟两种Tarjan算法,一种是low[u] = min( low[u], low[v] );,一种是low[u] = min( low[u], dfn[v] );。

第1种:

① dfs(1),dfn[1] = 1,low[1] = 1。

② dfs(2),dfn[2] = 2,low[2] = 2。

③ dfs(3),dfn[3] = 3,low[3] = 3。

④ 发现回边 3 -> 1,low[3] = 1。

⑤ dfs(4),dfn[4] = 4,low[4] = 4。

⑥ dfs(5),dfn[5] = 5,low[5] = 5。

⑦ 发现回边 5 -> 3,low[5] = 1。

⑧ dfs(5)结束,回到dfs(4),low[4] = 1。

⑨ dfs(4)结束,回到dfs(3),low[3] = 1。

⑩ dfs(3)结束,至此未发现割点。

第2种:

① dfs(1),dfn[1] = 1,low[1] = 1。

② dfs(2),dfn[2] = 2,low[2] = 2。

③ dfs(3),dfn[3] = 3,low[3] = 3。

④ 发现回边 3 -> 1,low[3] = 1。

⑤ dfs(4),dfn[4] = 4,low[4] = 4。

⑥ dfs(5),dfn[5] = 5,low[5] = 5。

⑦ 发现回边 5 -> 3,low[5] = 3。

⑧ dfs(5)结束,回到dfs(4),low[4] = 3。

⑨ dfs(4)结束,回到dfs(3),low[4] >= dfn[3],发现割点3,low[3] = 1。

而这个图中,正确答案是:3是割点。

看法与解释:
1、 问题: 上面我们会发现,第①个是错的,错在 low[5] = 3 而并非等于 1 。而站在low数组方面来讲,明明节点 5 可以不通过 4–> 5 就访问到节点 1 呀,而low定义就是最早能绕到的节点,也就是 1 呀。这么看确实是没错,而对比第②个过程你会发现,对于 3–>4 这条边,由于 low[4]<dfn[3],而无法证明 节点 3 是割点(接下来有说)。
2、 在这里自己的理解是: 由于判断节点 u 是否为割点时,判断的是 low[v] 与 dfn[u] 的关系,是建立在 u–>v 这条边的基础上的。而我们low[]数组的含义应该是:最早绕的祖先节点且不通过父节点! 因为我们判断的是low[v]与dfn[u]的关系,其含义就是如果去掉 父节点 u ,v 能到达祖先节点low[v]。而如果我们low[]定义的是最早绕的祖先节点却通过了父节点,此时会遗漏割点 u 。

在上面的过程①中我们发现:
由于5–>3中,3是已访问的点,low[5]=min(low[5],low[3])=1。
使得在递归返回后,low[4]=min(low[5],low[4])=low[5]=1。
与过程②对比发现,这里会使得low[4]==1,说明4 可以不通过父节点 3 就可以到达 祖先节点 1 ,而在图中是 ** 非法的 ** !这时候导致 low[4]<dfn[3] , 说明 节点 4 可以不通过 3 绕到 3 的祖先节点,所以 3 不是割点,而这是错误的!
综上,必须是与 dfn[] 作对比。

代码如下:

#include<iostream>
#include<algorithm>
#include<cstring>
#define MAXN 20008
using namespace std;
int n,m;
int cnt,t,son;
int dfn[MAXN],low[MAXN],head[MAXN];
bool vis[MAXN];
struct Edge
{
    int to;
    int next;
}edge[200008];
inline void add(int u,int v)
{
    edge[++cnt].to=v;
    edge[cnt].next=head[u];
    head[u]=cnt;
    return;
}
inline void init()
{
    cnt=t=son=0;
    memset(head,0,sizeof(head));
    memset(vis,0,sizeof(vis));
    memset(low,0,sizeof(low));
    memset(dfn,0,sizeof(dfn));
    return;
}
inline void tarjan(int u,int root)
{
    dfn[u]=low[u]=++t;
    for(int i=head[u];i;i=edge[i].next)
    {
        int v=edge[i].to;
        if(!dfn[v]){
            tarjan(v,u);
            if(u==root) son++;
            low[u]=min(low[u],low[v]);
            if(low[v]>=dfn[u]&&u!=root){
                vis[u]=true;
            }
        }else{
            low[u]=min(low[u],dfn[v]);
        }
    }
    if(u==root&&son>1) vis[u]=true;
}
int main()
{
    while(~scanf("%d%d",&n,&m))
    {
        int A,B;
        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]){
                son=0;
                tarjan(i,i);
            }
        }
        int sum=0;
        for(int i=1;i<=n;i++){
            if(vis[i]){
                sum++;
            }
        }
        printf("%d\n",sum);
        for(int i=1;i<=n;i++){
            if(vis[i]){
                printf("%d ",i);
            }
        }
        putchar('\n');
    }
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值