下面介紹中無向圖中割點和橋的概念:
割點:一個結點稱為割點(或者割頂)當且僅當去掉該節點極其相關的邊之后的子圖不連通。
橋:一條邊稱為橋(或者割邊)當且僅當去掉該邊之后的子圖不連通。
首先我們考慮一個連通圖(非連通圖可以分別考慮連通塊),我們從任意一個起點開始進行深度優先搜索,可以得到一棵樹,并且這棵樹中所有結點的子樹之間不存在邊,即沒有跨越兩棵子樹的邊(考慮一下,如果存在,那么與深度優先搜索樹的定義互相矛盾)。于是有如下定理:
在無向連通圖G中,
1、根結點u為割頂當且僅當它有兩個或者多個子結點;
2、非根結點u為割頂當且僅當u存在子結點v,使得v極其所有后代都沒有反向邊可以連回u的祖先(連回u不算)
在Tarjan算法里面,有兩個時間戳非常重要,一個是dfn,意為深度優先數,即代表訪問順序;一個是low,意為通過反向邊能到達的最小dfn。于是,上述定理中第二個條件(非根結點)可以簡單地寫成low[v]>=dfn[u]。
Network
割點
#include <cstdio>
#include<cstring>
#include<vector>
using namespace std;
const int MAXN=110;
vector<int> graph[MAXN];
int dfn[MAXN];
int low[MAXN];
int dfn_clock;
int isCut[MAXN];
int tarjan(int u,int fa)
{
int lowu=dfn[u]=++dfn_clock;
int child=0;
for(int i=0;i<graph[u].size();i++)
{
int v=graph[u][i];
if(dfn[v]==0)
{
child++;
int lowv=tarjan(v,u);
lowu=min(lowv,lowu);
if(lowv>=dfn[u])
{
isCut[u]=1;
}
}
else if(dfn[u]>dfn[v]&&v!=fa)
{
lowu=min(dfn[v],lowu);
}
}
if(fa<0&&child==1) isCut[u]=0;
low[u]=lowu;
return lowu;
}
int main()
{
int n;
int u,v;
char c;
while(scanf("%d",&n)!=EOF&&n)
{
memset(graph,0,sizeof(graph));
dfn_clock=0;
while(true)
{
scanf("%d",&u);
if(u==0) break;
getchar();
while(true)
{
scanf("%d",&v);
graph[u].push_back(v);
graph[v].push_back(u);
c=getchar();
if(c=='\n') break;
}
}
memset(dfn,0,sizeof(dfn));
memset(isCut,0,sizeof(isCut));
tarjan(1,-1);
int sum=0;
for(int i=1;i<=n;i++)
{
if(isCut[i]) sum++;
}
printf("%d\n",sum);
}
return 0;
}
橋:
橋的求法其實也是類似的,它的求法可以看成是割頂的一種特殊情況,當結點u的子結點v的后代通過反向邊只能連回v,那么刪除這條邊(u, v)就可以使得圖G非連通了。用Tarjan算法里面的時間戳表示這個條件,就是low[v]>dfn[u]。
int n,stamp,dfn[1005],low[1005];
int cnt,ansx[10005],ansy[10005];
vector<int> vec[1005];
int rank[1005];
void addAns(int x,int y)
{
if(x>y)
swap(x,y);
ansx[cnt]=x, ansy[cnt]=y;
cnt++;
}
void tarjan(int index,int fa)
{
int tmp;
dfn[index]=low[index]=++stamp;
for(int i=0;i<vec[index].size();i++)
{
tmp=vec[index][i];
if(!dfn[tmp])
{
tarjan(tmp,index);
low[index]=min(low[index],low[tmp]);
if(low[tmp]>dfn[index])
addAns(index,tmp);
}
else if(dfn[tmp]<dfn[index] && tmp!=fa)
{
low[index]=min(low[index],dfn[tmp]);
}
}
}