1. 程式人生 > >Tarjan演算法三大應用之雙連通分量

Tarjan演算法三大應用之雙連通分量

基本概念

  • 定義1:

    割點集合:點集VV,若G刪除了V後不連通,但刪除了V的任意真子集後G仍然連通,則稱V為割點集合

    割點:若某一結點就構成了割點集合,那麼稱此結點為割點關節點

    點連通度:點數最少的割點集合

    割邊集合:邊集EE,若G刪除了E後不連通,但刪除了E的任意真子集後G仍然連通,則稱E為割邊集合

    割邊:若某一結點就構成了割邊集合,那麼稱此邊為割邊關節邊

    邊連通度:邊數最少的割邊集合

    塊:沒有割點的極大連通子圖

  • 定義2:

    如果一個無向連通圖的點連通度大於1,則稱該圖是點雙連通的,簡稱雙連通

    。通俗來說就是不存在割點。

    如果一個無向連通圖的邊連通度大於1,則稱該圖是邊雙連通的,也簡稱雙連通。通俗來說就是不存在割邊。

    可以看出,點雙連通與邊雙連通都可以簡稱為雙連通,它們之間是有著某種聯絡的。(雙連通圖一定既是點雙連通的又是邊雙連通的)

  • 定義3:

    在圖G的所有子圖G中,如果G是雙連通的,則稱G雙連通子圖。如果一個雙連通子圖G它不是任何一個雙連通子圖的真子集,則G極大雙連通子圖

    雙連通分量,就是圖的極大雙連通子圖。特殊的,點雙連通分支又叫做

tarjan演算法求割點和橋

tarjan演算法求解雙連通分量的做法和求解有向圖的強連通分量做法類似。

  • 點雙連通分量

    求點雙連通分量也就是去找割點

    在dfs搜尋樹中,如果點k滿足下列兩個條件之一,那麼k是割點:

    1. k 為深搜樹的根,且 k 的兒子個數2
    2. k 為深搜樹的中間節點(k 既不是根也不是葉),且low[son]>=dfn[k]

    第一個條件很好理解,第二個條件son表示k的兒子,這句話的意思也就是說k的子孫中不會有某個點有追溯到k的祖先的邊。

    這樣,對於求點連通分量演算法中low值的嚴格定義將更新為:

    low(u)=Min
    {
    ​ dfn(u)
    ​ low[v] v是u 的一個兒子
    ​ dfn[v] v與u鄰接,且邊(u,v)是一條返祖邊
    }

    演算法具體細節見程式碼:

    void tarjan(int p,int u)
    {
        dfn[u] = low[u] = ++ti; // 時間戳
        int son = 0; // 當前節點兒子的個數
        for (k=head[u]; k!=-1; k=edge[k].next) //前向星實現
        {
            int v = edge[k].to;
            if (v == p)
                continue;
            if (dfn[v] == 0)
            {
                son++;
                tarjan(u,v);
                if (low[v] < low[u])
                    low[u] = low[v];
                if((u!=1 && dfn[u]<=low[v]) || (u==1&&son>=2))
                    istcc[u]=1; // 條件符合,u是割點
            }
            else
                low[u] = min(low[u],dfn[v]);
        }
    }
  • 邊雙連通分量

    求邊雙連通分量也就是去找割邊

    割邊的求解過程和求割點的方法類似,判斷方法是:

    無向圖中的一條邊(u,v)是割邊,當且僅當(u,v)是生成樹中的邊,且滿足dfn[u]<low[v]

    找到一條割邊就將割邊下面的邊連通分量出棧。

    stack<int> St;
    void Tarjan(int p,int u)
    {
          dfn[u]=low[u]=++ti;
          St.push(u);
          vis[u]=1;
          for(int k=head[u];k!=-1;k=edge[k].next)
          {
                int v=edge[k].v;
                if(v==p)continue;
                if(!dfn[v])
                {
                    Tarjan(u,v);
                    low[u]=min(low[u],low[v]);
                    if(low[v] > dfn[u]){  
                        cnt_bri++;//雙聯通分量塊的個數,多一個橋即多一個雙聯通分量  
                }
                else// if(vis[v]==1)//v在棧中,說明(u,v)是返祖邊
                {
                    low[u]=min(low[u],dfn[v]);
                }
            }
          if(dfn[u]==low[u])//將u所在的邊連通分量出棧
          {
               int x;
               ++cnt;//cnt表示縮點後的連通分量
               while(1)
               {
                     x=St.top();
                     St.pop();
                     vis[x]=0;
                     belong[x]=cnt;
                     if(x==u)break;
               }
          }
    }