淺談圖的割點和割邊(Tarjan)


 快要NOIP了,想着復習一下圖論,然后就發現不太會寫割點和割邊了,而且之前還沒有寫過博客,所以今天來填個坑

割點

首先是割點,什么是割點呢

就是在一個連通的無向圖中,把一個點去掉之后,圖就不再連通,去掉的這個點就是割點

我們來舉一個例子:

 

 

顯而易見,上面這個圖的割點是2

還有兩點要注意的

1.割點可能有很多個

2.無向圖和有向圖都有割點

然后我們來看一看如何實現割點

方案一:枚舉去掉每個點的情況,DFS判斷時間復雜度O(n^2)

方案二:Tarjan實現

如果你不會Tarjan,可與去看看我的一篇博客

傳送門:https://www.cnblogs.com/WWHHTT/p/9744658.html

好了,現在我們假定大家都已經學會了Tarjan,沒學的趕緊去學

其實割點就是在Tarjan的基礎上加了幾個判斷

首先是用一個狀態fa來記錄當前點的父親

然后再搜索過程中記錄以當前節點為根的子樹有多少個

判斷一共有兩種

(1)當前點是根時,它的子樹必須要超過兩個,否則不是割點。

至於為什么,我們看上面的圖

我們定義1為整個圖的根

然后去掉1時

圖是這樣的:

大家都很和諧,沒有了1,照樣過的很好(這是我見過根節點被黑的最慘的一次

(2)當前點不是根時,如果它有一個兒子在不經過它的情況下,不能夠回到它的父親,那么這個點就是割點

舉例:

上圖我們把2去掉后

是這個樣子滴:

大家都跟對方沒了聯系(莫名想到春物的是不是沒救了

既然已經明白了規則

下面來看看具體的實現過程吧

還是用剛才的圖(方便我畫) (紅色的點表示當前搜到的節點)

我們從點1開始搜

首先是到了1的唯一的兒子2

然后發現2也有兒子,就繼續搜,然后到了3

 

這時我們發現3唯一的連邊是2

更新low[3]

此時 DFN[1]=1

        LOW[1]=1

        DFN[2]=2;

        LOW[2]=2;

        DFN[3]=3;

        LOW[3]=2;

        DFN[4]=0;

        LOW[4]=0;

因為3沒有兒子了,代表low[3]的值已經不會變了,比較low[3]和dfn[2],我們發現,3不經過2無法回到dfs序更更高的點

所以2是割點

然后我們來到了2的第二個兒子4

和3的情況一樣,更新loe[4]

此時 DFN[1]=1

        LOW[1]=1

        DFN[2]=2;

        LOW[2]=2;

        DFN[3]=3;

        LOW[3]=2;

        DFN[4]=4;

        LOW[4]=2;

4現在也沒有兒子了,比較low[4]和dfn[2],判定2為割點,回溯

現在我們又回到了2

此時除了1(是2父親),2的兒子已經都跑完了,現在我們已知2有兩個子樹

但是2不是根節點,所以沒用,繼續回溯

現在又回到了1

我們得到了1的子樹數量為1,就算1是根節點,還是沒用(好可憐)

於是割點就跑完了

下面給出一組數據:

第一行n,m,代表有n個點,m條邊

之后m行,每行兩個數x,y,代表x,y之間有一條無向邊

4 3

1 2

2 3

2 4

輸出

所有的割點

2

下面給出代碼:(有注釋哦)

#include<iostream>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<algorithm>
using namespace std;
inline int min(int a,int b){return a<b?a:b;}
inline int max(int a,int b){return a>b?a:b;}
inline int rd(){
    int x=0,f=1;
    char c=getchar();
    for(;!isdigit(c);c=getchar()) if(c=='-') f=-1;
    for(;isdigit(c);c=getchar()) x=x*10+c-'0';
    return x*f;
}
inline void write(int x){
     if(x<0) putchar('-'),x=-x;
     if(x>9) write(x/10);
     putchar(x%10+'0');
}
int n,m;
int head[100006];
int nxt[200006],to[200006];
int total=0;
void add(int x,int y){
    total++;
    to[total]=y;
    nxt[total]=head[x];
    head[x]=total;
    return ;
}
int dfn[100006],low[100006];
int tot=0;
int book[100006];
void tarjan(int x,int fa){//記錄一個fa,代表當前點的父親 
    dfn[x]=low[x]=++tot;
    int child=0;//以x為根的子樹的個數 
    for(int e=head[x];e;e=nxt[e]){
        if(!dfn[to[e]]){
            tarjan(to[e],x);//擴展 
            low[x]=min(low[x],low[to[e]]);//回溯low 
            if(x==fa) child++;//只有在當前點是根時用記錄child 
            if(x!=fa&&low[to[e]]>=dfn[x]) book[x]=1;//如果一個點不是根,並且它的兒子在不經過它的情況下無法回到它的父親,那么它也是割點 
        }
        else if(to[e]!=fa) low[x]=min(low[x],dfn[to[e]]);//如果一個點被搜索過了並且是x的兒子或者是它的父親,就可以直接回溯 
    }
    if(x==fa&&child>=2) book[x]=1;//如果一個點是根並且有多於兩個子樹,就是割點 
    return ;
}
int main(){
    n=rd(),m=rd();
    for(int i=1;i<=m;i++){
        int x=rd(),y=rd();
        add(x,y);//無向圖 
        add(y,x);
    }
    for(int i=1;i<=n;i++) if(!dfn[i]) tarjan(i,i);//防止圖不連通 
    for(int i=1;i<=n;i++) if(book[i]) printf("%d ",i);//一個圖可能有多個割點 
    return 0;
}

 

 

割邊

我們來看看割邊

割邊呢,也稱作橋,和個點很像,指的是在一個圖中,去掉某一條邊后,這個圖不聯通了

舉個例子:

上面這個圖,把中間的邊去掉之后,1和2就不連通了,所以邊1->2就是割邊

具體的實現也和割點類似

只不過少一種判斷而已

割邊是非常好判斷的,因為不存在根之類的東西

一條邊是不是割邊只需要判斷這條邊連接的兩個節點是否可以從其他的路徑連通

我們再舉一個例子:

在剛才的圖上增加一個點3,分別連接1,2

此時我們去掉1->2

仍然有一條路徑可以從2到達1,所以1->就不是割邊了

具體的實現過程就不呈現了(反正就算這樣也沒有比我寫得更詳細的了

上組數據吧

和之前的規定一樣

2 1

1 2

輸出

1->2

下面給出代碼:

#include<iostream>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<algorithm>
using namespace std;
inline int min(int a,int b){return a<b?a:b;}
inline int max(int a,int b){return a>b?a:b;}
inline int rd(){
    int x=0,f=1;
    char c=getchar();
    for(;!isdigit(c);c=getchar()) if(c=='-') f=-1;
    for(;isdigit(c);c=getchar()) x=x*10+c-'0';
    return x*f;
}
inline void write(int x){
     if(x<0) putchar('-'),x=-x;
     if(x>9) write(x/10);
     putchar(x%10+'0');
}
int n,m;
int head[100006];
int nxt[200006],to[200006];
int total=0;
void add(int x,int y){
    total++;
    to[total]=y;
    nxt[total]=head[x];
    head[x]=total;
    return ;
}
int dfn[100006],low[100006];
int tot=0;
void tarjan(int x,int fa){
    dfn[x]=low[x]=++tot;
    for(int e=head[x];e;e=nxt[e]){
        if(!dfn[to[e]]){
            tarjan(to[e],x);//擴展 
            low[x]=min(low[x],low[to[e]]);//回溯low 
            if(low[to[e]]>dfn[x]) printf("%d->%d\n",x,to[e]);//如果兩點之間只有這一條路徑,那么它是割邊 
        }
        else if(to[e]!=fa) low[x]=min(low[x],dfn[to[e]]);//如果一個點被搜索過了並且是x的兒子或者是它的父親,就可以直接回溯 
    }
    return ;
}
int main(){
    n=rd(),m=rd();
    for(int i=1;i<=m;i++){
        int x=rd(),y=rd();
        add(x,y);//無向圖 
        add(y,x);
    }
    for(int i=1;i<=n;i++) if(!dfn[i]) tarjan(i,i);
    return 0;
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM