淺談圖論(二)——割點與割邊


今天我們接着搞圖論:割點和割邊

(一)割點

啥叫割點?

針對無向連通圖,若刪除一個點后使得該圖不連通,則該點是割點。

注意:一個圖中可能有多個割點

 

先上一組數據:

 6 7

1 4
1 3
4 2
3 2
2 5
2 6

5 6

圖是這樣的:

 

很容易看出結果是:

2

 

那么如何求出圖中的割點呢?

Algorithm1:dfs或bfs暴搜,不推薦也不想講

Algorithm2:

我們可以從任意一個頂點開始遍歷,用一個num數組來儲存每個頂點是第幾個訪問到的。(有個專業術語叫時間戳)

上面一組數據的num是這樣的:

    1 2 3 4 5 6
num 1 3 2 4 5 6

我們在遍歷所有點時會遇到割點(廢話),主要是如何認定一個點是割點。假設訪問到了k點,如果在沒有訪問過的點中,至少有一個點在不經過k點的情況下,無法回到已訪問過的點,則k點是割點。(因為該圖刪除點k后不連通了)

算法核心:如何判斷未被訪問過的點u在不經過點k的情況下能否返回任何一個已訪問過的點。

從樹的角度來看,k是u的父親,u是k的兒子,判斷u能否不經過k而回到它的所有祖先。

我們用數組low來表示每個點在不經過父節點的前提下,能返回的最早的時間戳。

上面一組數據的low是這樣的:

    1 2 3 4 5 6
low 1 1 1 1 3 3

首先枚舉k,再枚舉跟k有邊相連的u,如果存在low[u]>=num[k],即返回祖先必須經過k,則k是割點。

整個過程可以用dfs來實現。

下面呈上代碼:

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
int n,m,e[1005][1005],root,num[1005],low[1005],flag[1005],index;

void dfs(int cur,int dad)
{
    int i,child=0;
    index++;
    num[cur]=index;
    low[cur]=index;
    for(i=1;i<=n;i++)
    {
        if(e[cur][i]==1)
        {
            if(num[i]==0)
            {
                child++;
                dfs(i,cur);
                low[cur]=min(low[cur],low[i]);
                if(cur!=root && low[i]>=num[cur])
                {
                    flag[cur]=1;
                }
                else if(cur==root && child==2)
                {
                    flag[cur]=1;
                }
            }
            else if(low[i]!=dad)
            {
                low[cur]=min(low[cur],num[i]);
            }
        }
    }
}
int main()
{
    int i,a,b,c;
    scanf("%d%d",&n,&m);
    memset(e,0,sizeof(e));
    for(i=1;i<=m;i++)
    {
        scanf("%d%d",&a,&b);
        e[a][b]=1;
        e[b][a]=1;
    }
    root=1;
    dfs(1,root);
    return 0;
}
假裝是割點

有人可能發現,上面使用鄰接矩陣來存圖,這是不對的。因為這會導致該算法的時間復雜度為O(n^2)(跟暴搜差不多),算法就沒有意義了。所以應該用鄰接表來儲存圖,時間復雜度可以降到O(M+N)。

真正的代碼:

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
int n,m,root,num[1005],low[1005],flag[1005],index;

int a[2005],b[2005],next[2005],first[2005];
void dfs(int cur,int dad)//待判斷頂點和它的父節點 
{
    int i,child=0;
    index++;//index儲存的是時間戳 
    num[cur]=index;
    low[cur]=index;
    for(i=first[cur];i!=-1;i=next[i])//鄰接表優化
    {
        if(num[b[i]]==0)//i是cur的兒子 
        {
            child++;
            dfs(b[i],cur);//繼續遍歷 
            low[cur]=min(low[cur],low[b[i]]);//更新low的值
            if(cur!=root && low[b[i]]>=num[cur])//cur是割點
            {
                flag[cur]=1;
            }
            else if(cur==root && child>=2)/*或者cur是祖先且它有兩個兒子,那么它也是割點(刪除它后兩個兒子不連通) */
            {
                flag[cur]=1;
            }
        }
        else if(b[i]!=dad)//i是cur的祖先,更新low 
        {
            low[cur]=min(low[cur],num[b[i]]);
        }
    }
}
int main()
{
    int i,x,y,c;
    scanf("%d%d",&n,&m);
    for(i=1;i<=n;i++) first[i]=-1;
    for(i=1;i<=m;i++)
    {
        scanf("%d%d",&x,&y);
        a[i]=x;
        b[i]=y;
    }
    for(i=m+1;i<=2*m;i++)//注意是無向圖哦
    {
        a[i]=b[i-m];
        b[i]=a[i-m];
    }
    for(i=1;i<=2*m;i++)
    {
        next[i]=first[a[i]];
        first[a[i]]=i;
    }
    root=1;
    dfs(1,root);
    for(i=1;i<=n;i++)
    {
        if(flag[i]==1) printf("%d ",i);
    }
    return 0;
}
割點

 

 

(二)割邊

割邊也成為橋,與割點類似

針對無向連通圖,若刪除一條邊后使得該圖不連通,則該邊是割邊。

同樣的,一個圖中可能有多條割邊

 

同樣先上一組數據:

6 6

1 4

1 3

4 2

3 2

2 5

5 6

圖是這樣的:

該圖有兩條割邊:

5-6

2-5

 

割邊的求法也與割點類似,只需將

if(low[i]>=num[cur])

改為:

if(low[i]>num[cur])

就行了,即該點到除了達不了它的任何祖先,也無法到達它的父親。
好了,呈上代碼,請注意輸出部分:

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
int n,m,e[1005][1005],num[1005],low[1005];
int root,index;

void dfs(int cur,int dad)//待判斷頂點和它的父節點 
{
    int i;
    index++;//index儲存的是時間戳 
    num[cur]=low[cur]=index;
    for(i=1;i<=n;i++) 
    {
        if(e[cur][i]==1)
        {
            if(num[i]==0)//i是cur的兒子 
            {
                dfs(i,cur);//繼續遍歷 
                low[cur]=min(low[cur],low[i]);//更新low的值 
                if(low[i]>num[cur])
                    printf("%d-%d\n",cur,i);
            } 
            else if(i!=dad)//i是cur的祖先,更新low 
                low[cur]=min(low[cur],num[i]);
        }
    }
}
int main()
{
    int i,a,b;
    scanf("%d%d",&n,&m);
    for(i=1;i<=m;i++)//注意是無向圖哦 
    {
        scanf("%d%d",&a,&b);
        e[a][b]=1;
        e[b][a]=1;
    }
    root=1;
    dfs(1,root);
    return 0;
}
假裝是割邊

 同割點一樣,割邊算法也應該用鄰接表來存圖,否則該算法就無意義了。使用鄰接表后,這個算法的時間復雜度也是O(M+N)。

真.代碼如下:

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
int n,m,a[1005],b[1005],first[1005],next[1005],num[1005],low[1005];
int root,index;

void dfs(int cur,int dad)//待判斷頂點和它的父節點 
{
    int i;
    index++;//index儲存的是時間戳 
    num[cur]=low[cur]=index;
    for(i=first[cur];i;i=next[i]) 
    {
        if(num[b[i]]==0)//i是cur的兒子 
        {
            dfs(b[i],cur);//繼續遍歷 
            low[cur]=min(low[cur],low[b[i]]);//更新low的值 
            if(low[b[i]]>num[cur])
                printf("%d-%d\n",cur,b[i]);
        }
        else if(b[i]!=dad)//i是cur的祖先,更新low 
                low[cur]=min(low[cur],num[b[i]]);
    }
}
int main()
{
    int i,x,y;
    scanf("%d%d",&n,&m);
    for(i=1;i<=m;i++)//注意是無向圖哦 
    {
        scanf("%d%d",&x,&y);
        a[i]=x;
        b[i]=y;
    }
    for(i=m+1;i<=2*m;i++)
    {
        a[i]=b[i-m];
        b[i]=a[i-m];
    }
    for(i=1;i<=m*2;i++)
    {
        next[i]=first[a[i]];
        first[a[i]]=i;
    }
    root=1;
    dfs(1,root);
    return 0;
}
割邊

 

~祝大家編程順利~


免責聲明!

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



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