Tarjan求點雙連通分量


概述

在一個無向圖中,若任意兩點間至少存在兩條“點不重復”的路徑,則說這個圖是點雙連通的(簡稱雙連通,biconnected)

在一個無向圖中,點雙連通的極大子圖稱為點雙連通分量(簡稱雙連通分量,Biconnected Component,BCC)

 

性質

  1. 任意兩點間至少存在兩條點不重復的路徑等價於圖中刪去任意一個點都不會改變圖的連通性,即BCC中無割點
  2. 若BCC間有公共點,則公共點為原圖的割點
  3. 無向連通圖中割點一定屬於至少兩個BCC,非割點只屬於一個BCC

 

算法

 在Tarjan過程中維護一個棧,每次Tarjan到一個結點就將該結點入棧,回溯時若目標結點low值不小於當前結點dfn值就出棧直到目標結點(目標結點也出棧),將出棧結點和當前結點存入BCC

(說實話我覺得存點不比存邊難理解和實現啊……下面會解釋)

 

理解

首先申明一下,在我找到的BCC資料中,在算法實現中均將兩個點和一條邊構成的圖稱為BCC,此文章也沿用此的規定

如下圖:

我猜想可能是因為割點的定義,此圖中兩個點均不為割點,所以此圖也屬於BCC?

總之做題時注意題面要求,若要求的不含此種BCC則判斷每個BCC的大小即可

無向連通圖中割點一定屬於至少兩個BCC,非割點只屬於一個BCC

有了上面的規定我們也不難理解這一條了:割點就算相鄰也會屬於至少兩個BCC;BCC間的交點都是割點,所以非割點只屬於一個BCC

到一個結點就將該結點入棧

為什么用棧存儲呢?因為DFS是由上到下的,而分離BCC是自下而上的,需要后進先出的數據結構——棧

回溯時若目標結點low值不小於當前結點dfn值就出棧直到目標結點(目標結點也出棧),將出棧結點和當前結點存入BCC

對於每個BCC,它在DFS樹中最先被發現的點一定是割點或DFS樹的樹根

證明:割點是BCC間的交點,故割點在BCC的邊緣,且BCC間通過割點連接,所以BCC在DFS樹中最先被發現的點是割點;特殊情況是對於開始DFS的點屬於的BCC,其最先被發現的點就是DFS樹的樹根

上面的結論等價於每個BCC都在其最先被發現的點(一個割點或樹根)的子樹中

 這樣每發現一個BCC(low[v]>=dfn[u]),就將該子樹出棧,並將該子樹和當前結點(割點或樹根)加入BCC中。上面的操作與此描述等價

(我就是因為這個條件“將子樹出棧”沒理解寫錯了結果調了一晚上poj2942)

 

綜上,存點是不是很好理解?存邊雖然不會涉及重復問題(割點屬於至少兩個BCC),但會有很多無用操作。個人覺得存點也是個不錯的選擇。

 

模板

並沒有模板題

 1 #include<cstdio>
 2 #include<cctype>
 3 #include<vector>
 4 using namespace std;
 5 struct edge
 6 {
 7     int to,pre;
 8 }edges[1000001];
 9 int head[1000001],dfn[1000001],dfs_clock,tot;
10 int num;//BCC數量 
11 int stack[1000001],top;//
12 vector<int>bcc[1000001];
13 int tarjan(int u,int fa)
14 {
15     int lowu=dfn[u]=++dfs_clock;
16     for(int i=head[u];i;i=edges[i].pre)
17         if(!dfn[edges[i].to])
18         {
19             stack[++top]=edges[i].to;//搜索到的點入棧 
20             int lowv=tarjan(edges[i].to,u);
21             lowu=min(lowu,lowv);
22             if(lowv>=dfn[u])//是割點或根 
23             {
24                 num++;
25                 while(stack[top]!=edges[i].to)//將點出棧直到目標點 
26                     bcc[num].push_back(stack[top--]);
27                 bcc[num].push_back(stack[top--]);//目標點出棧 
28                 bcc[num].push_back(u);//不要忘了將當前點存入bcc 
29             }
30         }
31         else if(edges[i].to!=fa)
32             lowu=min(lowu,dfn[edges[i].to]);
33     return lowu;
34 }
35 void add(int x,int y)//鄰接表存邊 
36 {
37     edges[++tot].to=y;
38     edges[tot].pre=head[x];
39     head[x]=tot;
40 }
41 int main()
42 {
43     int n,m;
44     scanf("%d%d",&n,&m);
45     for(int i=1;i<=m;i++)
46     {
47         int x,y;
48         scanf("%d%d",&x,&y);
49         add(x,y),add(y,x);
50     }
51     for(int i=1;i<=n;i++)//遍歷n個點tarjan 
52         if(!dfn[i])
53         {
54             stack[top=1]=i;
55             tarjan(i,i);
56         }
57     for(int i=1;i<=num;i++)
58     {
59         printf("BCC#%d: ",i);
60         for(int j=0;j<bcc[i].size();j++)
61             printf("%d ",bcc[i][j]);
62         printf("\n");
63     }
64     return 0;
65 }
雙連通分量

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM