今天我們接着搞圖論:割點和割邊
(一)割點
啥叫割點?
針對無向連通圖,若刪除一個點后使得該圖不連通,則該點是割點。
注意:一個圖中可能有多個割點
先上一組數據:
6 7
1 4
1 3
4 2
3 2
2 5
2 6
5 6
圖是這樣的:
很容易看出結果是:
2
那么如何求出圖中的割點呢?
Algorithm1:dfs或bfs暴搜,不推薦也不想講
Algorithm2:
我們可以從任意一個頂點開始遍歷,用一個num數組來儲存每個頂點是第幾個訪問到的。(有個專業術語叫時間戳)
上面一組數據的num是這樣的:
1 2 3 4 5 6 num 1 3 2 4 5 6
我們在遍歷所有點時會遇到割點(廢話),主要是如何認定一個點是割點。假設訪問到了k點,如果在沒有訪問過的點中,至少有一個點在不經過k點的情況下,無法回到已訪問過的點,則k點是割點。(因為該圖刪除點k后不連通了)
算法核心:如何判斷未被訪問過的點u在不經過點k的情況下能否返回任何一個已訪問過的點。
從樹的角度來看,k是u的父親,u是k的兒子,判斷u能否不經過k而回到它的所有祖先。
我們用數組low來表示每個點在不經過父節點的前提下,能返回的最早的時間戳。
上面一組數據的low是這樣的:
1 2 3 4 5 6
low 1 1 1 1 3 3
首先枚舉k,再枚舉跟k有邊相連的u,如果存在low[u]>=num[k],即返回祖先必須經過k,則k是割點。
整個過程可以用dfs來實現。
下面呈上代碼:

#include<iostream> #include<cstdio> #include<cstdlib> #include<cstring> #include<algorithm> #include<cmath> using namespace std; int n,m,e[1005][1005],root,num[1005],low[1005],flag[1005],index; void dfs(int cur,int dad) { int i,child=0; index++; num[cur]=index; low[cur]=index; for(i=1;i<=n;i++) { if(e[cur][i]==1) { if(num[i]==0) { child++; dfs(i,cur); low[cur]=min(low[cur],low[i]); if(cur!=root && low[i]>=num[cur]) { flag[cur]=1; } else if(cur==root && child==2) { flag[cur]=1; } } else if(low[i]!=dad) { low[cur]=min(low[cur],num[i]); } } } } int main() { int i,a,b,c; scanf("%d%d",&n,&m); memset(e,0,sizeof(e)); for(i=1;i<=m;i++) { scanf("%d%d",&a,&b); e[a][b]=1; e[b][a]=1; } root=1; dfs(1,root); return 0; }
有人可能發現,上面使用鄰接矩陣來存圖,這是不對的。因為這會導致該算法的時間復雜度為O(n^2)(跟暴搜差不多),算法就沒有意義了。所以應該用鄰接表來儲存圖,時間復雜度可以降到O(M+N)。
真正的代碼:

#include<iostream> #include<cstdio> #include<cstdlib> #include<cstring> #include<algorithm> #include<cmath> using namespace std; int n,m,root,num[1005],low[1005],flag[1005],index; int a[2005],b[2005],next[2005],first[2005]; void dfs(int cur,int dad)//待判斷頂點和它的父節點 { int i,child=0; index++;//index儲存的是時間戳 num[cur]=index; low[cur]=index; for(i=first[cur];i!=-1;i=next[i])//鄰接表優化 { if(num[b[i]]==0)//i是cur的兒子 { child++; dfs(b[i],cur);//繼續遍歷 low[cur]=min(low[cur],low[b[i]]);//更新low的值 if(cur!=root && low[b[i]]>=num[cur])//cur是割點 { flag[cur]=1; } else if(cur==root && child>=2)/*或者cur是祖先且它有兩個兒子,那么它也是割點(刪除它后兩個兒子不連通) */ { flag[cur]=1; } } else if(b[i]!=dad)//i是cur的祖先,更新low { low[cur]=min(low[cur],num[b[i]]); } } } int main() { int i,x,y,c; scanf("%d%d",&n,&m); for(i=1;i<=n;i++) first[i]=-1; for(i=1;i<=m;i++) { scanf("%d%d",&x,&y); a[i]=x; b[i]=y; } for(i=m+1;i<=2*m;i++)//注意是無向圖哦 { a[i]=b[i-m]; b[i]=a[i-m]; } for(i=1;i<=2*m;i++) { next[i]=first[a[i]]; first[a[i]]=i; } root=1; dfs(1,root); for(i=1;i<=n;i++) { if(flag[i]==1) printf("%d ",i); } return 0; }
(二)割邊
割邊也成為橋,與割點類似
針對無向連通圖,若刪除一條邊后使得該圖不連通,則該邊是割邊。
同樣的,一個圖中可能有多條割邊
同樣先上一組數據:
6 6
1 4
1 3
4 2
3 2
2 5
5 6
圖是這樣的:
該圖有兩條割邊:
5-6
2-5
割邊的求法也與割點類似,只需將
if(low[i]>=num[cur])
改為:
if(low[i]>num[cur])
就行了,即該點到除了達不了它的任何祖先,也無法到達它的父親。
好了,呈上代碼,請注意輸出部分:

#include<iostream> #include<cstdio> #include<cstdlib> #include<cstring> #include<algorithm> #include<cmath> using namespace std; int n,m,e[1005][1005],num[1005],low[1005]; int root,index; void dfs(int cur,int dad)//待判斷頂點和它的父節點 { int i; index++;//index儲存的是時間戳 num[cur]=low[cur]=index; for(i=1;i<=n;i++) { if(e[cur][i]==1) { if(num[i]==0)//i是cur的兒子 { dfs(i,cur);//繼續遍歷 low[cur]=min(low[cur],low[i]);//更新low的值 if(low[i]>num[cur]) printf("%d-%d\n",cur,i); } else if(i!=dad)//i是cur的祖先,更新low low[cur]=min(low[cur],num[i]); } } } int main() { int i,a,b; scanf("%d%d",&n,&m); for(i=1;i<=m;i++)//注意是無向圖哦 { scanf("%d%d",&a,&b); e[a][b]=1; e[b][a]=1; } root=1; dfs(1,root); return 0; }
同割點一樣,割邊算法也應該用鄰接表來存圖,否則該算法就無意義了。使用鄰接表后,這個算法的時間復雜度也是O(M+N)。
真.代碼如下:

#include<iostream> #include<cstdio> #include<cstdlib> #include<cstring> #include<algorithm> #include<cmath> using namespace std; int n,m,a[1005],b[1005],first[1005],next[1005],num[1005],low[1005]; int root,index; void dfs(int cur,int dad)//待判斷頂點和它的父節點 { int i; index++;//index儲存的是時間戳 num[cur]=low[cur]=index; for(i=first[cur];i;i=next[i]) { if(num[b[i]]==0)//i是cur的兒子 { dfs(b[i],cur);//繼續遍歷 low[cur]=min(low[cur],low[b[i]]);//更新low的值 if(low[b[i]]>num[cur]) printf("%d-%d\n",cur,b[i]); } else if(b[i]!=dad)//i是cur的祖先,更新low low[cur]=min(low[cur],num[b[i]]); } } int main() { int i,x,y; scanf("%d%d",&n,&m); for(i=1;i<=m;i++)//注意是無向圖哦 { scanf("%d%d",&x,&y); a[i]=x; b[i]=y; } for(i=m+1;i<=2*m;i++) { a[i]=b[i-m]; b[i]=a[i-m]; } for(i=1;i<=m*2;i++) { next[i]=first[a[i]]; first[a[i]]=i; } root=1; dfs(1,root); return 0; }
~祝大家編程順利~