圖的割點與割邊
割點
什麽是割點?
如果在一個圖中,如果把一個點刪除,那麽這個圖不再聯通,那麽這個點就是割點(割頂),當然是在無向圖。
如何實現?
如果我們嘗試刪除每個點,並且判斷這個圖的聯通性,那麽復雜度會特別的高。所以要介紹一個常用的算法:\(Tarjan\)。
首先,我們上一個圖:
很容易的看出割點是 2,而且這個圖僅有這一個割點。
首先,我們按照 \(DFS\) 序給他打上時間戳(訪問的順序)。
這些信息被我們保存在一個叫做 num
的數組中。
還需要另外一個數組 low
,用它來存儲不經過其父親(你有多個那麽就看你遍歷到了哪個)能到達的時間戳。
例如 2 的話是 1, 5 和 6 是 3。
然後我們開始 \(DFS\)
另外,如果搜到了自己(在環中),如果他有兩個及以上的兒子,那麽他一定是割點了,如果只有一個兒子,那麽把它刪掉,不會有任何的影響。比如下面這個圖,此處形成了一個環,從樹上來講它有 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\) 的用途等。
圖的割點與割邊