定義及概念
在一個無向圖G中,存在一個點集V,從圖G中刪掉所有屬於V的點及其與之相連的邊,G不連通。如果有一個邊集E,刪掉所有屬於這個集合的邊,G不連通。
- 點連通度:最小V的點數
- 邊連通度:最小E的邊數
- 割點:點連通度為1時,V的唯一元素
- 割邊(橋):邊連通度為1時,E的唯一元素
- 點雙連通:任意兩點間,存在兩條或以上路徑,且路徑上的點互不重復。(點連通度大於1即可)
- 邊雙連通:任意兩點間,存在兩條或以上路徑,且路徑上的邊互不重復。(邊連通度大於1即可)
- 雙連通分量:在圖G中的子圖G‘,是一個雙連通子圖,它不是其他雙連通子圖的真子集,則它是一個雙連通分量。
- 邊雙連通分量一定是點雙連通,反之不成立。如下圖,不存在割點,即為點雙連通,但不是邊雙連通。
推論
- 有割點不一定有割邊
- 有割邊不一定有割點
-
兩個割點之間的邊不一定是割邊
-
割邊的端點不一定是割點
-
一個圖可能有多個割點,有多個橋
tarjan算法
定義
- 樹枝邊:DFS搜索樹上的邊
- 前向邊:與DFS方向相同的邊
- 返祖邊:與DFS方向相反的邊
割點
- 關於DFS搜索樹的根節點,如果它有兩棵或兩棵以上的子樹,則它是割點。
- DFS樹不存在橫叉邊,所以多棵子樹不可能通過直接連通。
- 關於其他節點,如果其子孫的返祖邊,只能連到自己或自己的子孫,而連不到它的祖先,那此節點為割點。
#include <cstdio>
#include <cstring>
#include <algorithm>
#define MAXN 100005
int head[MAXN];
struct edge{
int v,next;
}G[MAXN<<1];
int tot = 0;
int dfn[MAXN],low[MAXN],num = 0;
int ans[MAXN];
int N,M;
inline void add(int u,int v){
G[++tot].v = v;G[tot].next = head[u];head[u] = tot;
}
void tarjan(int u,int fa){
dfn[u] = low[u] = ++num;
int count = 0;//統計子樹數量
for(register int i=head[u];i;i=G[i].next){
int v = G[i].v;
if(!dfn[v]){
tarjan(v,u);
++count;//子樹數量+1
low[u] = std::min(low[u],low[v]);
if(low[v]>=dfn[u]&&u!=fa)ans[u] = 1;// 如果u不是根節點
}
low[u] = std::min(low[u],dfn[v]);
}
if(count>=2&&u==fa)ans[u] = 1;//如果u是根節點,且有兩個及兩個以上的子樹
}
int main(){
std::memset(head,0,sizeof(head));
std::memset(ans,0,sizeof(ans));
std::memset(dfn,0,sizeof(dfn));
scanf("%d%d",&N,&M);
int u,v;
for(register int i=1;i<=M;++i){
scanf("%d%d",&u,&v);
add(u,v);add(v,u);
}
for(register int i=1;i<=N;++i){
if(!dfn[i])tarjan(i,i);
}
int count = 0;
for(register int i=1;i<=N;++i){
if(ans[i])++count;
}
printf("%d\n",count);
for(register int i=1;i<=N;++i){
if(ans[i])printf("%d ",i);
}
return 0;
}
割邊
- 返祖邊:一定不是割邊,刪掉此邊后不影響圖連通。
- 關於一條邊(u,v),若此邊之下的子樹有返祖邊,且這條返祖邊能回到u的祖先及u,則不是割邊