您的位置:首页 > 其它

Stoer-Wagner求无向图全局最小割

2013-10-07 19:55 323 查看




[b]最小割Stoer-Wagner算法

割:在一个图G(V,E)中V是点集,E是边集。在E中去掉一个边集C使得G(V,E-C)不连通,C就是图G(V,E)的一个割;

最小割:在G(V,E)的所有割中,边权总和最小的割就是最小割。

求G的任意s-t最小割Min-C(s,t):

设s,t是途中的两个点且边(s,t)∈E(即s,t之间存在一条边)。如果G的最小割Cut把G分成M,N两个点集

①:如果s∈M,t∈N则Min-C(s,t)=
Cut(不讨论)

②:如果s,t∈M(或者s,t∈N)则Min-C(s,t)<=
Cut

我们来定义一个Contract(a,b)操作,即把a,b两个点合并,表示为删除节点b,把b的节点信息添加到a上

如下图是做了Contract(5,6)









对于所点v有w(v,5)+=w(v,6)

求s-t最小割的方法

定义w(A,x) = ∑w(v[i],x),v[i]∈A

定义Ax为在x前加入A的所有点的集合(不包括x)

1.令集合A={a},a为V中任意点

2.选取V-A中的w(A,x)最大的点x加入集合

3.若|A|=|V|,结束,否则更新w(A,x),转到2

令倒数第二个加入A的点为s,最后一个加入A的点为t,则s-t最小割为w(At,t)

以Poj (pku) 2914 Minimum Cut

的第三个case为例,图为





G(V,E)

我们设法维护这样的一个w[],初始化为0;

我们把V-A中的点中w[i]最大的点找出来加入A集合;

V-A直到为空

w[]的情况如下
w[i]
0
1
2
3
4
5
6
7
初始值
0
0
0
0
0
0
0
0
A=A∪{0}
---
1
1
1
1
0
0
0
A=A∪{1}
---
2
2
1
0
0
0
A=A∪{2}
---
3
1
0
0
0
A=A∪{3}
---
1
0
0
1
A=A∪{4}
---
1
1
2
A=A∪{7}
2
2
---
A=A∪{5}
---
3
A=A∪{6}
---
每次w[i]+=∑(i,a)的权值a∈A

记录最后加入A的节点为t=6,倒数第二个加入A的为s=5,则s-t的最小割就为w[s],在图中体现出来的意思就是5-6的最小割为w[s]=3

然后我们做Contract(s,t)操作,得到下图




G(V’,E’)

重复上述操作

w[i]
0
1
2
3
4
5
7
初始值
0
0
0
0
0
0
0
A=A∪{0}
---
1
1
1
1
0
0
A=A∪{1}
---
2
2
1
0
0
A=A∪{2}
---
3
1
0
0
A=A∪{3}
---
1
0
1
A=A∪{4}
---
2
2
A=A∪{5}
---
4
A=A∪{7}
---
s=5,t=7 s-t最小割是4

Contract(s,t)得到





w[i]
0
1
2
3
4
5
初始值
0
0
0
0
0
0
A=A∪{0}
---
1
1
1
1
0
A=A∪{1}
---
2
2
1
0
A=A∪{2}
---
3
1
0
A=A∪{3}
---
1
1
A=A∪{4}
---
4
A=A∪{5}
---
s=4,t=5 s-t最小割是4

Contract(s,t)得到





w[i]
0
1
2
3
4
初始值
0
0
0
0
0
A=A∪{0}
---
1
1
1
1
A=A∪{1}
---
2
2
1
A=A∪{2}
---
3
1
A=A∪{3}
---
2
A=A∪{4}
---
s=3,t=4 s-t最小割是2,(此时已经得出答案,以下省略)

AC代码:

#include
<iostream>

#include <stdio.h>

#include <string.h>

#include <queue>

#define INT_MAX 0x3f3f3f3f

using namespace std;

int mp[502][502];

int N,M;

bool combine[502];

int minC=INT_MAX;

void search(int &s,int &t){

bool vis[502];

int w[502];

memset(vis,0,sizeof(vis));

memset(w,0,sizeof(w));

int tmpj=1000;

for(int i=0;i<N;i++){

int max=-INT_MAX;

for(int j=0;j<N;j++){

if(!vis[j]&&!combine[j]&&max<w[j]){

max=w[j];

tmpj=j;

}

}

if(t==tmpj){minC=w[t];return;}

vis[tmpj]=1;

s=t,t=tmpj;

for(int j=0;j<N;j++){

if(!vis[j]&&!combine[j])

w[j]+=mp[t][j];

}

}

minC=w[t];

}

int mincut(){

int ans=INT_MAX;

int s,t;

memset(combine,0,sizeof(combine));

for(int i=0;i<N-1;i++){

s=t=-1;

search(s,t);

combine[t]=true;

ans=ans>minC?minC:ans;

for(int j=0;j<N;j++){

mp[s][j]+=mp[t][j];

mp[j][s]+=mp[j][t];

}

}

return ans;

}

int main(){

//freopen("in.txt","r",stdin);

while(cin>>N>>M){

memset(mp,0,sizeof(mp));

int u,v,w;

while(M--){

scanf("%d
%d %d",&u,&v,&w);

mp[u][v]+=w;

mp[v][u]+=w;

}

cout<<mincut()<<endl;

}

return 0;

}


hdu3691Nubulsa Expo(Stoer-Wagner求无向图全局最小割)

分类: 图论2013-10-04
17:57 128人阅读 评论(0) 收藏 举报

图论

题目请戳这里

题目大意:给一张图,n个点,m条无向边,每条边有权值,表示该路人流量上界。给定起点s,问如何选终点t,能是s-t的所有路径上最小人流量总和最大,给出这个最大流量。

题目分析:根据最大流最小割定理,此题就是求一个最小割。给定的起点是无用信息,因为起点一定在某个割集中,那么终点在另一个割集随便找一点即可。所以此题求的是一个全局最小割。最大流可以解决。但需要O(n)枚举终点。再加上最大流的复杂度,至少要O(n^4),对于此题来说复杂度偏高,所以要找其他算法。

Stoer-Wagner算法是求无向图全局最小割的一个有效算法,最坏时间复杂度O(n^3),主要思想是先找任意2点的最小割,然后记录下这个最小割,再合并这2个点。这样经过n-1次寻找任意2点最小割,每次更新全局最小割,最后整张图缩成一个点,算法结束,所保存下来的最小割就是全局最小割。

Stoer-Wagner的正确性:

设s和t是图G的2个顶点,图G的全局最小割要么是s-t的最小割,此时s和t在G的全局最小割的2个不同的子集中,或者是G中将s和t合并得的的新图G'的全局最小割,此时s和t在G的全局最小割的同一个子集中。所以只需要不断求出当前图中任意2个点的最小割,然后合并这2个点。不断缩小图的规模求得最小割。

关于更详细的Stoer-Wagner算法:

1:这是英文版论文,英语太烂,没勇气看,不过里面有个插图蛮好的,可以很直观的体会这个算法的工作过程。

2:这篇给了一点证明

3:看看吧

详情请见代码:

[cpp] view
plaincopy

#include <iostream>

#include<cstdio>

#include<cstring>

#include<algorithm>

using namespace std;

const int N = 305;

const int M = 50005;

const int inf = 0x3f3f3f3f;

int g

,v
,dis
;

bool vis
;

int m,n,s;

void build()

{

int a,b,c;

memset(g,0,sizeof(g));

while(m --)

{

scanf("%d%d%d",&a,&b,&c);

g[a] += c;

g[b][a] += c;

}

}

void solve()

{

int i,j;

int ans = inf;

int maxx,maxi;

int s,t;

for(i = 1;i <= n;i ++)

v[i] = i;//初始化点集

while(n > 1)

{

int cur,pre;

cur = 1;

memset(dis,0,sizeof(dis));

memset(vis,false,sizeof(vis));

for(i = 2;i <= n;i ++)

{

dis[v[i]] = g[v[1]][v[i]];

}

vis[v[1]] = true;

for(i = 1;i < n;i ++)

{

maxx = -1;

maxi = 0;

for(j = 1;j <= n;j ++)

{

if(vis[v[j]] == false && maxx < dis[v[j]])

{

maxx = dis[v[j]];//找离当前集合最远的点

maxi = j;

}

}

vis[v[maxi]] = true;

if(i == n - 2)

s = maxi;

if(i == n - 1)

t = maxi;

for(j = 1;j <= n;j ++)

{

if(vis[v[j]] == false)

dis[v[j]] += g[v[maxi]][v[j]];

}

}

ans = min(ans,dis[v[t]]);

for(i = 1;i <= n;i ++)

{

g[v[s]][v[i]] += g[v[t]][v[i]];

g[v[i]][v[s]] = g[v[s]][v[i]];

}

v[maxi] = v
;

n --;

}

printf("%d\n",ans);

}

int main()

{

while(scanf("%d%d%d",&n,&m,&s),n)

{

build();

solve();

}

return 0;

}

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: