【學習筆記】圖論 割點 割邊
阿新 • • 發佈:2019-01-04
演算法介紹
Tarjan_割點
- 適用範圍:無向圖
- 功能:給定無向圖G=(V,E),求出一個點集合V’,包含圖中所有的割點。
- 時間複雜度:O(N+E),N為圖中點數,E為圖中邊數。
Tarjan_橋
- 適用範圍:無向圖
- 功能:給定無向圖G=(V,E),求出一個邊集合E’,包含圖中所有的橋。
- 時間複雜度:O(N+E),N為圖中點數,E為圖中邊數。
演算法講解
一些概念:
- 點連通度:去掉最少的點使得圖分為若干聯通分支。只有點連通度為1的圖有割點.
- 割點:若去掉某個節點將會使圖分為若干聯通分支,則改點稱為割點。
- 邊連通度:去掉最少的邊使得圖分為若干聯通分支。只有邊連通度為1的圖有橋。
- 橋:若去掉某條邊將會使圖分為若干聯通分支,則改邊稱為橋。
現實意義:
通訊網路中,用來衡量系統可靠性,連通度越高,可靠性越高。
割點
- 暴力求解,依次刪除每一個節點v,用DFS(或者BFS)判斷是否連通,再把節點加入圖中。若用鄰接表(adjacency list),需要做V次DFS,時間複雜度為O(V∗(V+E))。
這個演算法複雜度太高,我們需要去改進它,我們想:能否一遍DFS求解? - Tarjan演算法
我們不難發現:一個頂點u是割點,當且僅當滿足(1)或(2)
(1) u為根節點,且u有多於一個子樹。
(2) u為非根節點,u有一個子節點s,且沒有任何從s或s的後代節點指向v的真祖先的後向邊。
對於根節點我們可以進行特判,那麼非根節點我們如何處理呢?
思路:
我們定義DNF[v]為v結點的入時間戳
橋:
- 思路和求割點一樣,也需要dfn陣列和low陣列輔助,只是不用判根,(u,v)是橋當且僅當DFN[u]< low[v],因為若u有一個子節點v,v及它的子孫所能到達的節點不超過v,及無法到u以上,那麼這條樹邊就是橋了。
- 需要注意的是重邊情況,若有兩條邊(1,2),(2,1),那麼都不是橋但是若只有一條(1,2),則是橋。但是在處理的時候若只按照(v!=fa)判斷,這兩條邊只算了一條,我們需要的是不重複計算同一條邊
CODE
割點模板:
// luogu P3388
#include<bits/stdc++.h>
using namespace std;
const int MAXN=100010,MAXM=100010;
int Head[MAXN],Next[MAXM*2],To[MAXM*2];
bool vis[MAXN],cutv[MAXN];
int dfn[MAXN],low[MAXN];
int n,m,tot,tim,root,rootson;
void add_eage(int x,int y)
{
tot++;
Next[tot]=Head[x];
Head[x]=tot;
To[tot]=y;
}
void ReadInfo()
{
scanf("%d%d",&n,&m);
tim=tot=0;
for (int i=1;i<=m;i++)
{
int x,y;
scanf("%d%d",&x,&y);
add_eage(x,y); add_eage(y,x);
}
}
void Tarjan(int u,int pre)
{
dfn[u]=low[u]=++tim;
vis[u]=true;
for (int i=Head[u];i;i=Next[i])
{
int v=To[i];
if (!vis[v])
{
Tarjan(v,u);
low[u]=min(low[u],low[v]);
if (u!=root && dfn[u]<=low[v]) cutv[u]=true;
else if (u==root && ++rootson==2) cutv[u]=true;
}
else if (v!=pre) low[u]=min(low[u],dfn[v]);
}
}
void solve()
{
memset(dfn,-1,sizeof(dfn));
memset(low,-1,sizeof(low));
memset(vis,false,sizeof(vis));
memset(cutv,false,sizeof(cutv));
for (int i=1;i<=n;i++)
if (!vis[i])
{
root=i;
rootson=0;
Tarjan(i,0);
}
}
void Outit()
{
int num=0;
for (int i=1;i<=n;i++)
if (cutv[i]) num++;
printf("%d\n",num);
for (int i=1;i<=n;i++)
if (cutv[i]) printf("%d ",i);
printf("\n");
}
int main()
{
ReadInfo();
solve();
Outit();
return 0;
}
橋模板(可處理重邊):
#include<bits/stdc++.h>
using namespace std;
const int MAXN=100010,MAXM=100010;
int Head[MAXN],Next[MAXM*2],To[MAXM*2];
bool vis[MAXN];
int dfn[MAXN],low[MAXN];
int n,m,tot,tim,num_cutedge;
struct Edge{
int u,v;
}cutedge[MAXM];
void add_eage(int x,int y)
{
tot++;
Next[tot]=Head[x];
Head[x]=tot;
To[tot]=y;
}
void ReadInfo()
{
memset(Head,0,sizeof(Head));
scanf("%d%d",&n,&m);
tim=tot=0;
for (int i=1;i<=m;i++)
{
int x,y;
scanf("%d%d",&x,&y);
add_eage(x,y); add_eage(y,x);
}
}
void Tarjan(int u,int id)
{
dfn[u]=low[u]=++tim;
vis[u]=true;
for (int i=Head[u];i;i=Next[i])
{
int v=To[i];
if (!vis[v])
{
Tarjan(v,i);
low[u]=min(low[u],low[v]);
if (dfn[u]<low[v]) cutedge[++num_cutedge]=(Edge){u,v};
}
else if ((i+1)/2!=(id+1)/2) low[u]=min(low[u],dfn[v]);
}
}
void solve()
{
memset(dfn,-1,sizeof(dfn));
memset(low,-1,sizeof(low));
memset(vis,false,sizeof(vis));
num_cutedge=0;
for (int i=1;i<=n;i++)
if (!vis[i]) Tarjan(i,0);
}
void Outit()
{
printf("the number of the bridges is %d\n",num_cutedge);
for (int i=1;i<=num_cutedge;i++)
printf("%d %d\n",cutedge[i].u,cutedge[i].v);
}
int main()
{
ReadInfo();
solve();
Outit();
return 0;
}