圖的割點(邊)演算法
阿新 • • 發佈:2019-01-01
參考啊哈演算法第八章第三節,第四節:
先介紹圖的割點演算法
採用兩種儲存方法:
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]]);