1. 程式人生 > >圖的割點(邊)演算法

圖的割點(邊)演算法

參考啊哈演算法第八章第三節,第四節:
先介紹圖的割點演算法
採用兩種儲存方法:
1.陣列儲存
2.圖的鄰接表法

第一種方法:

#include <stdio.h>

int min(int a,int b)
{
    return a<b?a:b;
}

int n,m,e[9][9];
int root,num[9],low[9],flag[9],index;//index用來進行時間戳的遞增

//割點演算法核心
void dfs(int cur,int father)
{
    int child=0,i;  //child用來記錄生成樹中當前頂點cur的兒子數量
index++; num[cur]=index;//當前cur的時間戳 low[cur]=index;//當前cur能訪問到最早的時間戳。剛開始肯定是自己啦。 for(i=1;i<=n;i++)//列舉與當前頂點cur有邊相連的頂點i { if(e[cur][i]==1) { if(num[i]==0)//如果當前頂點的時間戳為零說明當前頂點還沒有被訪問過 { child++; dfs(i,cur);//繼續往下深度優先遍歷搜尋,此時i為cur的兒子
//更新當前頂點cur能訪問到最早定點的時間戳,因為如果他的兒子比他訪問的還早,那麼他肯定不需要通過父親就能訪問到 low[cur]=min(low[cur],low[i]); //如果當前節點不是根節點並且滿足low[i]>=num[cur],則當前頂點為割點 if(cur!=root&&low[i]>=num[cur]) flag[cur]=1; if(cur==root&&child==2
) flag[cur]=1; } //如果當前頂點i曾經被訪問過,並且這個頂點不是當前頂點cur的父親。則說明i為cur的祖先, //因為i不可能為cur的兒子,如果為兒子,在第一個if語句中就判斷了,因此需要更新當前節點cur能訪問到最早節點的時間戳 //自反的相鄰邊不滿足。 else if(i!=father) low[cur]=min(low[cur],num[i]); } } return; } int main() { int x,y; scanf("%d%d",&n,&m); //初始化 for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) e[i][j]=0; //讀入每條邊 for(int i=1;i<=m;i++) { scanf("%d%d",&x,&y); e[x][y]=1; e[y][x]=1; } root=1; dfs(1,root);//從1號節點開始進行深度優先遍歷 for(int i=1;i<=n;i++) if(flag[i]) printf("%d ",i); return 0; }

對for迴圈裡面的做一些改變

if(i==cur||i==father||!e[cur][i]) continue;
//有兒子並且判斷兒子是否為祖宗。
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;
    if(cur==root&&child==2)
        flag[cur]=1;
}
else low[cur]=min(low[cur],num[i]);

採用圖的鄰接表儲存法:

#include <stdio.h>

int u[15],v[15],w[15];
int first[7],next[15];

int min(int a,int b)
{
    return a<b?a:b;
}

int n,m,e[9][9];
int root,num[9],low[9],flag[9],index;//index用來進行時間戳的遞增

//割點演算法核心
void dfs(int cur,int father)
{
    int child=0;  //child用來記錄生成樹中當前頂點cur的兒子數量
    index++;
    num[cur]=index;//當前cur的時間戳
    low[cur]=index;//當前cur能訪問到最早的時間戳。剛開始肯定是自己啦。
    //列舉與當前頂點cur有邊相連的頂點i
    int k=first[cur];
    while(k!=-1)
    {
        if(w[k]==1)
        {
            if(num[v[k]]==0)//如果當前頂點的時間戳為零說明當前頂點還沒有被訪問過
            {
                child++;
                dfs(v[k],cur);//繼續往下深度優先遍歷搜尋,此時i為cur的兒子
                //更新當前頂點cur能訪問到最早定點的時間戳,因為如果他的兒子比他訪問的還早,那麼他肯定不需要通過父親就能訪問到
                low[cur]=min(low[cur],low[v[k]]);
                //如果當前節點不是根節點並且滿足low[i]>=num[cur],則當前頂點為割點
                if(cur!=root&&low[v[k]]>=num[cur])
                    flag[cur]=1;
                if(cur==root&&child==2)
                    flag[cur]=1;
            }
            //如果當前頂點i曾經被訪問過,並且這個頂點不是當前頂點cur的父親。則說明i為cur的祖先,
            //因為i不可能為cur的兒子,如果為兒子,在第一個if語句中就判斷了,因此需要更新當前節點cur能訪問到最早節點的時間戳
            else if(v[k]!=father)
                low[cur]=min(low[cur],num[v[k]]);
        }
        k=next[k];
    }

    return;
}

int main()
{
    int x,y;
    scanf("%d%d",&n,&m);

    for(int i=1;i<=m;i++)
    {
        scanf("%d%d",&u[i],&v[i]);
        w[i]=1;
    }
    for(int i=m+1;i<=2*m;i++)
    {
        u[i]=v[i-m];
        v[i]=u[i-m];
        w[i]=1;
    }
    for(int i=1;i<=n;i++)
        first[i]=-1;
    for(int i=1;i<=2*m;i++)
    {
        next[i]=first[u[i]];
        first[u[i]]=i;
    }

    root=1;
    dfs(1,root);//從1號節點開始進行深度優先遍歷
    for(int i=1;i<=n;i++)
        if(flag[i])
            printf("%d ",i);
    return 0;
}
/*
6 7
1 4
1 3
4 2
3 2
2 5
2 6
5 6
*/

圖的割邊演算法:
只需要將上述程式改變一個條件

low[v]>=num[u]
改為
low[v]>num[u]

因為low[v]>=num[u]代表點v不可能在不經過父節點回到祖先的(包括父親),所以頂點u是割點,如果low[v]>num[u]則表示連父親都回不到了,倘若頂點v不能回到祖先,也沒有另外一條邊能回到父親,那麼u-v這條邊就是割邊,程式碼如下(只對上邊做做一些修改):

if(num[i]==0)
{
    dfs(i,cur);
    low[cur]=min(low[cur],low[i]);
    if(low[i]>num[cur])
        printf("%d-%d\n",cur,i);
}
else if(i!=father)
    low[cur]=min(low[cur],num[i]);
if(num[v[k]]==0)
{
    dfs(v[k],cur);
    low[cur]=min(low[cur],low[v[k]]);
    if(low[v[k]]>num[cur])
        printf("%d-%d\n",cur,v[k]);
}
else if(v[k]!=father)
    low[cur]=min(low[cur],num[v[k]]);