一.基本概念html
1.橋:若無向連通圖的邊割集中只有一條邊,則稱這條邊爲割邊或者橋 (離散書上給出的定義。。node
通俗的來講就是無向連通圖中的某條邊,刪除後獲得的新圖聯通分支至少爲2(即不連通;ios
2.割點:若無向連通圖的點割集中只有一個點,則稱這個點爲割點或者關節點 ;算法
通俗的來講就是無向連通圖中的某條邊,刪除後獲得的新圖連通分支至少爲2;ide
二:tarjan算法求割點和橋spa
1.割點:1)當前節點爲樹根的時候,條件是「要有多餘一棵子樹」;code
若是這有一顆子樹,去掉這個點也沒有影響,若是有兩顆子樹,去掉這點,兩顆子樹就不連通了;htm
2)當前節點u不是樹根的時候,條件是「low[v]>=dfn[u]」,也就是在u以後遍歷的點,可以向上翻,blog
最多到u,若是能翻到u的上方,那就有環了,去掉u以後,圖仍然連通。ip
2.橋:若一條無向邊(u,v)是橋,
1)當且僅當無向邊(u,v)是樹枝邊,須要知足dfn(u)<low(v),即v向上翻不到u及其以上的點,
那麼u--v之間必定可以有1條或者多條邊不能刪去, 由於他們之間有部分無環,是橋,
若是v能上翻到u那麼u--v就是一個環,刪除其中一條路徑後,仍然是連通的。
3.注意點:1)求橋的時候:由於邊是無方向的,因此父親孩子節點的關係須要本身規定一下,
在tarjan的過程當中if(v不是u的父節點) low[u]=min(low[u],dfn[v]);
由於若是v是u的父親,那麼這條無向邊就被誤認爲是環了。
2)找橋的時候:注意看看有沒有重邊,有重邊的邊必定不是橋,也要避免誤判。
4.也能夠先進行tarjan(),求出每個點的dfn和low,並記錄dfs過程當中的每一個點的父節點,
遍歷全部點的low,dfn來尋找橋和割點
代碼:
1 #include <iostream> 2 #include <stdio.h> 3 #include <vector> 4 #include <string.h> 5 using namespace std; 6 7 const int MAXN=1e5+10; 8 vector<int> mp[MAXN]; 9 bool is_cut[MAXN]; 10 int n, m, count=0; 11 int low[MAXN], dfn[MAXN], pre[MAXN];//pre[u]記錄u的父親節點編號 12 //dfn[u]記錄節點u在DFS過程當中被遍歷到的次序號,low[u]記錄節點u或u的子樹經過非父子邊追溯到最先的祖先節點(即DFS次序號最小 13 14 void tarjan(int u, int fu){ 15 pre[u]=fu;//記錄當前u的父親節點 16 dfn[u]=low[u]=count++; 17 for(int i=0; i<mp[u].size(); i++){ 18 int v=mp[u][i]; 19 if(dfn[v]==-1){ 20 tarjan(v, u); 21 low[u]=min(low[u], low[v]); 22 }else if(fu!=v){//若是v是u的父親的話,即有重邊,那麼不多是橋 23 low[u]=min(low[u], dfn[v]); 24 } 25 } 26 } 27 28 void solve(void){ 29 int rootson=0; 30 tarjan(1, 0); 31 for(int i=2; i<=n; i++){ 32 int v=pre[i]; 33 if(v==1){ 34 rootson++;//統計根節點的子樹個數,若其不小於2,即爲割點 35 }else if(low[i]>=dfn[v]){ 36 is_cut[v]=true; 37 } 38 } 39 if(rootson>1) is_cut[1]=true; 40 puts("割點爲:"); 41 for(int i=1; i<=n; i++){//輸出割點 42 if(is_cut[i]){ 43 printf("%d ", i); 44 } 45 } 46 puts("\n橋爲:"); 47 for(int i=1; i<=n; i++){ 48 int v=pre[i]; 49 if(v>0&&low[i]>dfn[v]){ 50 printf("%d %d\n", v, i); 51 } 52 } 53 puts(""); 54 } 55 56 int main(void){ 57 scanf("%d%d", &n, &m); 58 for(int i=0; i<m; i++){ 59 int x, y; 60 scanf("%d%d", &x, &y); 61 mp[x].push_back(y); 62 mp[y].push_back(x); 63 } 64 memset(dfn, -1, sizeof(dfn)); 65 memset(low, -1, sizeof(low)); 66 solve(); 67 return 0; 68 }
求橋的另外一種寫法(更快一點):
1 #include <iostream> 2 #include <stdio.h> 3 #include <string.h> 4 using namespace std; 5 6 const int MAXN = 1e5 + 10; 7 8 struct node{ 9 int v, next, use; 10 }edge[MAXN << 2]; 11 12 bool bridge[MAXN]; 13 int low[MAXN], dfn[MAXN], vis[MAXN]; 14 int head[MAXN], pre[MAXN], ip, sol, count; 15 16 void init(void){ 17 memset(head, -1, sizeof(head)); 18 memset(vis, false, sizeof(vis)); 19 memset(bridge, false, sizeof(bridge)); 20 count = sol = ip = 0; 21 } 22 23 void addedge(int u, int v){ 24 edge[ip].v = v; 25 edge[ip].use = 0; 26 edge[ip].next = head[u]; 27 head[u] = ip++; 28 } 29 30 void tarjan(int u){ 31 vis[u] = 1; 32 dfn[u] = low[u] = count++; 33 for(int i = head[u]; i != -1; i = edge[i].next){ 34 if(!edge[i].use){ 35 edge[i].use = edge[i ^ 1].use = 1; 36 int v = edge[i].v; 37 if(!vis[v]){ 38 pre[v] = u; 39 tarjan(v); 40 low[u] = min(low[u], low[v]); 41 if(dfn[u] < low[v]){ 42 sol++; 43 bridge[v] = true; 44 } 45 }else if(vis[v] == 1){ 46 low[u] = min(low[u], dfn[v]); 47 } 48 } 49 } 50 vis[u] = 2; 51 } 52 53 int main(void){ 54 int n, m, q, x, y, cas = 1; 55 while(~scanf("%d%d", &n, &m)){ 56 if(!n && !m) break; 57 init(); 58 for(int i = 0; i < m; i++){ 59 scanf("%d%d", &x, &y); 60 addedge(x, y); 61 addedge(y, x); 62 } 63 pre[1] = 1; 64 tarjan(1); 65 for(int i = 1; i <= n; i++){ 66 if(bridge[i]) cout << i << " " << pre[i] << endl; 67 } 68 } 69 return 0; 70 }