無(wú)向圖的雙連通分量

雙連通分量

點(diǎn)_雙連通分量 BCC
  對(duì)于一個(gè)連通圖,如果任意兩點(diǎn)至少存在兩條“點(diǎn)不重復(fù)”的路徑,則說(shuō)圖是點(diǎn)雙連通的(即任意兩條邊都在一個(gè)簡(jiǎn)單環(huán)中),點(diǎn)雙連通的極大子圖稱為點(diǎn)_雙連通分量分预。 通常來(lái)說(shuō),如果要求任意兩條邊在同一個(gè)簡(jiǎn)單環(huán)中,那么就是求點(diǎn)-雙連通
  易知每條邊屬于一個(gè)連通分量,且連通分量之間最多有一個(gè)公共點(diǎn)秕重,且一定是割頂叉袍。

#include <cstdio>
#include<cstring>
#include<vector>
using namespace std;
const int MAXN=10010;
vector<int> graph[MAXN];
int dfn[MAXN];//第一次訪問(wèn)的時(shí)間戳
int clocks;//時(shí)間戳
int isCut[MAXN];//標(biāo)記節(jié)點(diǎn)是否為割頂
struct Edge
{
    int u,v;
    Edge(){}
    Edge(int u,int v):u(u),v(v){}
};
vector<Edge> edge;//DFS訪問(wèn)過(guò)的邊
vector<int> bcc[MAXN];//點(diǎn)_雙連通分量
int bccno[MAXN];//節(jié)點(diǎn)屬于的點(diǎn)_雙連通分量的編號(hào)
int bcc_cnt;//點(diǎn)_雙連通分量的數(shù)目
int DFS(int u,int fa)
{
    int lowu=dfn[u]=++clocks;
    int child=0;
    for(int i=0;i<graph[u].size();i++)
    {
        int v=graph[u][i];
        Edge e(u,v);
        if(dfn[v]==0)
        {
            edge.push_back(e);
            child++;
            int lowv=DFS(v,u);
            lowu=min(lowv,lowu);//用后代更新lowu
            if(lowv>=dfn[u])//找到了一個(gè)子樹滿足割頂?shù)臈l件
            {
                isCut[u]=1;
                bcc_cnt++;
                bcc[bcc_cnt].clear();
                while(true)//保存bcc信息
                {
                    Edge ee=edge.back();
                    edge.pop_back();
                    //bccno[ee.u]!=bcc_cnt是為了防止重復(fù)加點(diǎn)
                    if(bccno[ee.u]!=bcc_cnt) { bcc[bcc_cnt].push_back(ee.u); bccno[ee.u]=bcc_cnt;}
                    if(bccno[ee.v]!=bcc_cnt) {bcc[bcc_cnt].push_back(ee.v);bccno[ee.v]=bcc_cnt;}
                    if(ee.u==u&&ee.v==v) break;
                }
            }
        }
        else if(dfn[v]<dfn[u]&&v!=fa) //用反向邊更新lowu
        {
            edge.push_back(e);
            lowu=min(lowu,dfn[v]);
        }
    }
    if(fa<0&&child==1) isCut[u]=0;
    return lowu;
}
void tarjan(int n)
{
    bcc_cnt=clocks=0;
    memset(dfn,0,sizeof(dfn));
    memset(isCut,0,sizeof(isCut));
    memset(bccno,0,sizeof(bccno));
    memset(bcc,0,sizeof(bcc));
    for(int i=1;i<=n;i++)
    {
        if(dfn[i]==0)
        {
            DFS(i,-1);
        }
    }
    for(int i=1;i<=bcc_cnt;i++)
    {
        for(int j=0;j<bcc[i].size();j++)
        {
            printf("%d  ",bcc[i][j]);
        }
        printf("\n");
    }
}
int main()
{
    int n,m,a,b;
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        memset(graph,0,sizeof(graph));
        for(int i=1;i<=m;i++)
        {
            scanf("%d%d",&a,&b);
            graph[a].push_back(b);
            graph[b].push_back(a);
        }
        tarjan(n);
    }
}

邊_雙連通分量 EBC
  邊雙連通分量是指任意兩點(diǎn)存在至少兩條"邊不重復(fù)"的路徑的圖,還可以理解為每條邊都至少在一個(gè)簡(jiǎn)單環(huán),即每條邊都不是橋色徘。如果要求某條邊被刪除了,但是圖G能夠在刪除任意一條邊后很洋,仍然是連通的失乾,當(dāng)且僅當(dāng)圖G至少為雙連通的。
  對(duì)于邊
雙連通分量的求解簡(jiǎn)單多了娩贷,我們先找出所有的橋第晰,并將其做上標(biāo)記。然后在利用dfs遍歷連通分量育勺,只需在遍歷時(shí)不訪問(wèn)橋即可。

  #include<cstdio>
  #include<cstring>
  #include<algorithm>
  using namespace std;
  const int MAXN=1010;
  const int MAXE=2010;
  struct Node
  {
      int to,next;
      bool cut;//邊是否為橋
  };
  Node edge[MAXE];
  int head[MAXN];
  int cnt;
  int dfn[MAXN];
  int low[MAXN];
  int clocks;
  int belong[MAXN];//點(diǎn)屬于哪個(gè)邊連通分量
  int blocks;//連通分量數(shù)
  void addEdge(int u,int v)
  {
      edge[cnt].to=v;
      edge[cnt].next=head[u];
      edge[cnt].cut=false;
      head[u]=cnt++;
  }
  //e是為了去重,e是邊在數(shù)組的位置
  //另一種去重為DFS(u,fa),v!=fa,但是有重邊時(shí)可能會(huì)判斷錯(cuò)誤
  //比如沒(méi)重邊時(shí),假設(shè)(a,b)是橋,但是如果(a,b)有重邊,那么(a,b)就不是橋了
  void DFS(int u,int e)//求出所有的橋
  {
    low[u]=dfn[u]=++clocks;
    for(int i=head[u];i!=-1;i=edge[i].next)
    {
        int v=edge[i].to;
        if(i==(e^1)) continue;//這里只會(huì)去重該邊的反邊,不會(huì)去它的重邊
        if(dfn[v]==0)
        {
            DFS(v,i);
            low[u]=min(low[u],low[v]);
            if(low[v]>dfn[u])
            {
                edge[i].cut=1;
                edge[i^1].cut=1;
            }
        }
        else if(dfn[v]<dfn[u])
        {
            low[u]=min(low[u],dfn[v]);
        }
    }
  }
  void DFS2(int u)//求出每個(gè)點(diǎn)所在的邊連通分量
  {
      dfn[u]=1;
      belong[u]=blocks;
      for(int i=head[u];i!=-1;i=edge[i].next)
      {
          if(edge[i].cut) continue;
          if(dfn[edge[i].to]==0) DFS2(edge[i].to);
      }
  }
  void work(int n)
  {
      memset(dfn,0,sizeof(dfn));
      memset(belong,0,sizeof(belong));
      clocks=blocks=0;
      for(int i=1;i<=n;i++)
      {
          if(dfn[i]==0)
          {
              DFS(i,-1);
          }
      }
      memset(dfn,0,sizeof(dfn));
      for(int i=1;i<=n;i++)
      {
          if(dfn[i]==0)
          {
              blocks++;
              DFS2(i);
          }
      }
  }
  int main()
  {
     int n,m,a,b;
     while(scanf("%d%d",&n,&m)!=EOF)
     {
         memset(head,-1,sizeof(head));
         cnt=0;
         for(int i=0;i<m;i++)
         {
             scanf("%d%d",&a,&b);
             addEdge(a,b);
             addEdge(b,a);
         }
         work(n);
      }
    }

還有一種辦法是棧模擬,直接在DFS里面求出邊連通分量,基于一個(gè)這樣的事實(shí),橋的端點(diǎn)的dfn[u]=low[u]
簡(jiǎn)要證明以下:
low[u]表示u及其后代能連回的最早的祖先的dfn值,所以low[u]<=dfn[u]總是成立罗岖。假設(shè)對(duì)于橋的一個(gè)端點(diǎn)u,low[u]!=dfn[u],即low[u]<dfn[u],說(shuō)明u可以連到u的父節(jié)點(diǎn)或者u通過(guò)u的某個(gè)子節(jié)點(diǎn)通過(guò)返祖邊連到u的父節(jié)點(diǎn),現(xiàn)在刪除這個(gè)橋,但是u還可以連接到u的父節(jié)點(diǎn)或者u通過(guò)u的子代通過(guò)返祖邊連回到u的父節(jié)點(diǎn),根據(jù)橋的性質(zhì),刪除了橋后u與u的父節(jié)點(diǎn)不能連通,這與橋的性質(zhì)有矛盾,所以假設(shè)不成立,即low[u]==dfn[u]

     #include<cstdio>
     #include<stack>
     #include<cstring>
     using namespace std;
     const int MAXN=1010;
     const int MAXE=2010;
     struct Node
     {
         int to,next;
         bool cut;
     };
     Node edge[MAXE];
     int head[MAXN];
     int low[MAXN],dfn[MAXN],onStack[MAXN];
     int cnt,clocks;
     stack<int> sta;
     int belong[MAXN];
     int blocks;
     void addEdge(int u,int v)
     {
         edge[cnt].to=v;
         edge[cnt].next=head[u];
         edge[cnt].cut=false;
         head[u]=cnt++;
     }
     void DFS(int u,int e)
     {
        low[u]=dfn[u]=++clocks;
        onStack[u]=1;
        sta.push(u);
        for(int i=head[u];i!=-1;i=edge[i].next)
        {
            int v=edge[i].to;
            if(e==(i^1)) continue;
            if(dfn[v]==0)
            {
                DFS(v,i);
                low[u]=min(low[u],low[v]);
                if(low[v]>dfn[u])
                {
                    edge[i].cut=true;
                    edge[i^1].cut=true;
                }
            }
            else if(onStack[v]&&dfn[v]<dfn[u])//v在棧中涧至,說(shuō)明(u,v)是返祖邊
            {
                low[u]=min(low[u],dfn[v]);
            }
        }
        if(dfn[u]==low[u])//說(shuō)明u是橋的端點(diǎn),所以將u所在的邊連通分量出棧
        {
            blocks++;
            while(true)
            {
                int curr=sta.top();
                sta.pop();
                onStack[curr]=0;//出棧
                belong[curr]=blocks;
                if(curr==u) break;
            }
        }
     }
     void work(int n)
     {
         memset(dfn,0,sizeof(dfn));
         memset(onStack,0,sizeof(onStack));
         clocks=0;
         while(!sta.empty()) sta.pop();
         memset(belong,0,sizeof(belong));
         blocks=0;
         for(int i=1;i<=n;i++)
         {
             if(dfn[i]==0)
             {
                 DFS(i,-1);
             }
         }
     }
     int main()
     {
         int n,m,a,b;
         while(scanf("%d%d",&n,&m)!=EOF)
         {
             memset(head,-1,sizeof(head));
             cnt=0;
             for(int i=0;i<m;i++)
             {
                 scanf("%d%d",&a,&b);
                 addEdge(a,b);
                 addEdge(b,a);
             }
             work(n);
         }
     }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市桑包,隨后出現(xiàn)的幾起案子南蓬,更是在濱河造成了極大的恐慌,老刑警劉巖哑了,帶你破解...
    沈念sama閱讀 218,546評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件赘方,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡弱左,警方通過(guò)查閱死者的電腦和手機(jī)窄陡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,224評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)拆火,“玉大人跳夭,你說(shuō)我怎么就攤上這事涂圆。” “怎么了币叹?”我有些...
    開封第一講書人閱讀 164,911評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵润歉,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我颈抚,道長(zhǎng)踩衩,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,737評(píng)論 1 294
  • 正文 為了忘掉前任贩汉,我火速辦了婚禮驱富,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘雾鬼。我一直安慰自己萌朱,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,753評(píng)論 6 392
  • 文/花漫 我一把揭開白布策菜。 她就那樣靜靜地躺著晶疼,像睡著了一般。 火紅的嫁衣襯著肌膚如雪又憨。 梳的紋絲不亂的頭發(fā)上翠霍,一...
    開封第一講書人閱讀 51,598評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音蠢莺,去河邊找鬼寒匙。 笑死,一個(gè)胖子當(dāng)著我的面吹牛躏将,可吹牛的內(nèi)容都是我干的锄弱。 我是一名探鬼主播,決...
    沈念sama閱讀 40,338評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼祸憋,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼会宪!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起蚯窥,我...
    開封第一講書人閱讀 39,249評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤掸鹅,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后拦赠,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體巍沙,經(jīng)...
    沈念sama閱讀 45,696評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,888評(píng)論 3 336
  • 正文 我和宋清朗相戀三年荷鼠,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了句携。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,013評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡允乐,死狀恐怖务甥,靈堂內(nèi)的尸體忽然破棺而出牡辽,到底是詐尸還是另有隱情,我是刑警寧澤敞临,帶...
    沈念sama閱讀 35,731評(píng)論 5 346
  • 正文 年R本政府宣布态辛,位于F島的核電站,受9級(jí)特大地震影響挺尿,放射性物質(zhì)發(fā)生泄漏奏黑。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,348評(píng)論 3 330
  • 文/蒙蒙 一编矾、第九天 我趴在偏房一處隱蔽的房頂上張望熟史。 院中可真熱鬧,春花似錦窄俏、人聲如沸蹂匹。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,929評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)限寞。三九已至,卻和暖如春仰坦,著一層夾襖步出監(jiān)牢的瞬間履植,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,048評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工悄晃, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留玫霎,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,203評(píng)論 3 370
  • 正文 我出身青樓妈橄,卻偏偏與公主長(zhǎng)得像庶近,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子眷蚓,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,960評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容