1. 程式人生 > >圖的割點與割邊

圖的割點與割邊

tor 不一定 cout 多個 就是 child http push_back 輸出結果

割點

什麽是割點?

如果在一個圖中,如果把一個點刪除,那麽這個圖不再聯通,那麽這個點就是割點(割頂),當然是在無向圖。

如何實現?

如果我們嘗試刪除每個點,並且判斷這個圖的聯通性,那麽復雜度會特別的高。所以要介紹一個常用的算法:\(Tarjan\)

首先,我們上一個圖:

技術分享圖片

很容易的看出割點是 2,而且這個圖僅有這一個割點。

首先,我們按照 \(DFS\) 序給他打上時間戳(訪問的順序)。

技術分享圖片

這些信息被我們保存在一個叫做 num 的數組中。

還需要另外一個數組 low,用它來存儲不經過其父親(你有多個那麽就看你遍歷到了哪個)能到達的時間戳。

例如 2 的話是 1, 5 和 6 是 3。

然後我們開始 \(DFS\)

,我們判斷某個點是否是割點的根據是:對於某個頂點 \(u\),如果存在至少一個頂點 \(v\)\(u\) 的兒子),使得 \(low_v>=num_u\),即不能回到祖先,那麽 \(u\) 點為割點。

另外,如果搜到了自己(在環中),如果他有兩個及以上的兒子,那麽他一定是割點了,如果只有一個兒子,那麽把它刪掉,不會有任何的影響。比如下面這個圖,此處形成了一個環,從樹上來講它有 2 個兒子:

技術分享圖片

我們在訪問 1 的兒子時候,假設先 \(DFS\) 到了 2,然後標記用過,然後遞歸往下,來到了 4, 4 又來到了 3,當遞歸回溯的時候,會發現 3 已經被訪問過了,所以不是割點。

更新 low

的偽代碼如下:

如果 v 是 u 的兒子
    low[u]=min(low[u],low[v]);
否則
    low[u] = min(low[u],num[v]);

洛谷 P3388 【模板】割點(割頂)

Code

/*
洛谷 P3388 【模板】割點(割頂)
算法: Tarjan
時間:2018 8 22
by @liyifeng 
*/
#include<bits/stdc++.h>
using namespace std;
int n,m;//n:點數 m:邊數 
int num[100001],low[100001],inde,res;
//num:記錄每個點的時間戳 low:能不經過父親到達最小的編號,inde:時間戳,res:答案數量 
bool vis[100001],flag[100001];//flag: 答案 vis:標記是否重復 
vector <int> edge[100001];// 存圖用的 
void Tarjan(int u,int father)//u 當前點的編號,father 自己爸爸的編號 
{
    vis[u]=true;// 標記 
    low[u]=num[u]=++inde;// 打上時間戳 
    int child=0;// 每一個點兒子數量 
    for(auto v : edge[u])// 訪問這個點的所有鄰居 (C++11) 
    {
        if(!vis[v])
        {
            child++;// 多了一個兒子 
            Tarjan(v,u);// 繼續 
            low[u]=min(low[u],low[v]);// 更新能到的最小節點編號 
            if(father!=u&&low[v]>=num[u]&&!flag[u])// 主要代碼 
            // 如果不是自己,且不通過父親返回的最小點符合割點的要求,並且沒有被標記過 
            // 要求即為:刪了父親連不上去了,即為最多連到父親
            {
                flag[u]=true;
                res++;// 記錄答案 
            }
        }
        else if(v!=father)
            low[u]=min(low[u],num[v]);// 如果這個點不是自己,更新能到的最小節點編號 
    }
    if(father==u&&child>=2&&!flag[u])// 主要代碼,自己的話需要 2 個兒子才可以 
    {
        flag[u]=true;
        res++;// 記錄答案 
    }
}
int main()
{
    cin>>n>>m;// 讀入數據 
    for(int i=1;i<=m;i++)// 註意點是從 1 開始的 
    {
        int x,y;
        cin>>x>>y;
        edge[x].push_back(y);
        edge[y].push_back(x);
    }// 使用 vector 存圖 
    for(int i=1;i<=n;i++)// 因為 Tarjan 圖不一定聯通 
        if(!vis[i])
        {
            inde=0;// 時間戳初始為 0 
            Tarjan(i,i);// 從第 i 個點開始,父親為自己 
        }
    cout<<res<<endl;
    for(int i=1;i<=n;i++)
        if(flag[i]) cout<<i<<" ";// 輸出結果 
    for(int i=1;i<=n;i++) cout<<low[i]<<endl;
    return 0;
}

割邊

什麽是割邊?

和割點差不多,還叫做割橋。

無向聯通圖中,去掉一條邊,圖中的連通分量數增加,則這條邊,稱為橋或者割邊,當然也是在無向圖。

實現

和割點差不多,只要改一處: \(low_v>num_u\) 就可以了,而且不需要考慮根節點的問題。

割邊是和是不是根節點沒關系的,原來我們求割點的時候是指點 \(v\) 是不可能不經過父節點 \(u\) 為回到祖先節點(包括父節點),所以頂點 \(u\) 是割點。如果 \(low_v==num_u\) 表示還可以回到父節點,如果頂點 \(v\) 不能回到祖先也沒有另外一條回到父親的路,那麽 \(u-v\) 這條邊就是割邊

\(Tarjan\) 算法還有許多用途,常用的例如求強連通分量,縮點,還有求 \(2-SAT\) 的用途等。

圖的割點與割邊