1. 程式人生 > >【學習筆記】圖論 割點 割邊

【學習筆記】圖論 割點 割邊

演算法介紹

Tarjan_割點

  • 適用範圍:無向圖
  • 功能:給定無向圖G=(V,E),求出一個點集合V’,包含圖中所有的割點。
  • 時間複雜度:O(N+E),N為圖中點數,E為圖中邊數。

Tarjan_橋

  • 適用範圍:無向圖
  • 功能:給定無向圖G=(V,E),求出一個邊集合E’,包含圖中所有的橋。
  • 時間複雜度:O(N+E),N為圖中點數,E為圖中邊數。

演算法講解

一些概念:

  1. 點連通度:去掉最少的點使得圖分為若干聯通分支。只有點連通度為1的圖有割點.
  2. 割點:若去掉某個節點將會使圖分為若干聯通分支,則改點稱為割點。
  3. 邊連通度:去掉最少的邊使得圖分為若干聯通分支。只有邊連通度為1的圖有橋。
  4. 橋:若去掉某條邊將會使圖分為若干聯通分支,則改邊稱為橋。

現實意義:

通訊網路中,用來衡量系統可靠性,連通度越高,可靠性越高。

割點

  1. 暴力求解,依次刪除每一個節點v,用DFS(或者BFS)判斷是否連通,再把節點加入圖中。若用鄰接表(adjacency list),需要做V次DFS,時間複雜度為O(V∗(V+E))。
    這個演算法複雜度太高,我們需要去改進它,我們想:能否一遍DFS求解?
  2. Tarjan演算法
    我們不難發現:一個頂點u是割點,當且僅當滿足(1)或(2)
    (1) u為根節點,且u有多於一個子樹。
    (2) u為非根節點,u有一個子節點s,且沒有任何從s或s的後代節點指向v的真祖先的後向邊。
    對於根節點我們可以進行特判,那麼非根節點我們如何處理呢?
    思路:
    我們定義DNF[v]為v結點的入時間戳
    ,即根據dfs序給節點進行編號,定義LOW[v]為v及v的子孫所能達到的最小節點的DFN,那麼判定v是否是節點就很方便了,只要u有一個兒子v,使得DNF[u]< =LOW[v],則u是割點。

橋:

  1. 思路和求割點一樣,也需要dfn陣列和low陣列輔助,只是不用判根,(u,v)是橋當且僅當DFN[u]< low[v],因為若u有一個子節點v,v及它的子孫所能到達的節點不超過v,及無法到u以上,那麼這條樹邊就是橋了。
  2. 需要注意的是重邊情況,若有兩條邊(1,2),(2,1),那麼都不是橋但是若只有一條(1,2),則是橋。但是在處理的時候若只按照(v!=fa)判斷,這兩條邊只算了一條,我們需要的是不重複計算同一條邊
    ,那麼如何判斷是不是同一條邊呢?鏈式前向星為我們提供了一種方法,因為邊儲存時同一條邊的序號是(1,2),(3,4),……這樣下去的,若((i+1)/2==(j+1)/2)則i,j是同一條邊,這樣就判斷出來了。

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;
}