tarjan 学习笔记

无向图的割顶和桥

  • low[i] 表示 i号节点能访问到最早节点的编号
  • dfn[i] 表示 i号节点在dfs序中的编号
void tarjan(int u,int fa){
    int v;
    int child = 0,k = 0;
    dfn[u] = low[u] = ++dfs_clock;
    for(int i=head[u];i!=-1;i=edge[i].nxt){
        v = edge[i].to;
        if(v == fa && !k){  // 处理重边 第一次访问到父亲则不访问
            k++;
            continue;
        }
        if(!dfn[v]){        // v是u的儿子
            child++;
            tarjan(v,u);
            low[u] = min(low[u],low[v]); // 儿子能访问到,父亲也能访问到
            if(low[v] > dfn[u]){    // 儿子不能访问到父亲前面的点
                // is_cut[i] = true;    // i号边为割边
            }
            if(low[v] >= dfn[u] && fa!=-1){ // 非树根且儿子不能访问他前面的点
                iscutpoint[u] = true;
            }
        }else{              // v已经被访问过, 即u可以绕过父亲来访问更靠前的点
            low[u] = min(low[u],dfn[v]);
        }
    }
    if(fa == -1 && child>1 ){   // 有超过两个儿子的树根一定是割点
        iscutpoint[u] = true;
    }
}

非根

如图 2的儿子连回了他的父亲1的父亲,导致1不能成为割点. 而若是2的儿子连回了1,则1仍然可以作为割点
对于桥,若2的儿子既不能连回1也不能连回1的父亲,即图中蓝色的边不存在,则1-2这条边即为割边

树根

不同子树之间只能通过树根相连,所以只要超过两颗子树树根就是割点

对于根的子树互相相连,可以认为只有一颗子树,其余与根相连的边均为反向边即可.

  • 割边:\(low[v] > dfn[u]\)
  • 割点:\(low[v] >= dfn[u]\) || (\(is_root\) && \(son_cnt>1\))

边-双联通分量

  • 定义: 任意两点存在至少两条"边不重复"的路径 => 所有的边均不是桥
  • 求法: 第一遍dfs找出所有的桥,第二遍dfs不经过桥,所经过的点均在同一双联通分量中
    // 或者同求强联通分量的方法,low[u] == dfn[u] 即进行缩点(不加u)
void tarjan(int u,int fa){
    dfn[u] = low[u] = ++stemp;
    s.push(u); in[u] = true;
    int son = 0,k = 1;
    for(int i=head[u];i;i=e[i].nxt){
        int v = e[i].to;
        if(v==fa && k){k = 0; continue;}    //   处理重边
        if(!dfn[v]){
            tarjan(v,u);
            low[u] = min(low[u],low[v]);
        }else if(in[v]){
            low[u] = min(low[u],dfn[v]);
        }
    }
    if(low[u] == dfn[u]){
        int v; ++cnt_block;
        do{
            v = s.top();    s.pop();
            belong[v] = cnt_block;
        }while(v!=u);
    }
}

点-双联通分量

  • 定义: 任意两点至少存在两条"点不重复"的路径 => 分量内部无割点
  • 求法: tarjan过程中遇到low[v] >= dfn[u] 将栈中所有点弹入一个联通分量(加入u但不将u弹出,因为u可能属于多个连通分量)
void tarjan(int u,int fa){
    dfn[u] = low[u] = ++stemp;
    s.push(u);
    int son = 0,k = 1;
    for(int i=head[u];i;i=e[i].nxt){
        int v = e[i].to;
        if(v==fa && k){k = 0; continue;}    //   处理重边
        if(!dfn[v]){
            tarjan(v,u);
            low[u] = min(low[u],low[v]);
            if(low[v] >= dfn[u]){
                if(fa != -1)    cut[u] = 1;
                blocks[++cnt_block].clear();
                blocks[cnt_block].push_back(u); // 加入u
                int now;
                do{
                    now = s.top();  s.pop();
                    blocks[cnt_block].push_back(now);
                }while(now != v);               //栈弹到v,并加入双联通分量中
            }
        }else{
            low[u] = min(low[u],dfn[v]);
        }
    }
    if(fa==-1 && son>1) cut[u] = 1;
}

posted @ 2019-08-18 13:13  新新人類  阅读(251)  评论(0编辑  收藏  举报