poj 2914: 斯托尔-王纳算法求解 最小切割问题 (Minimum Cut using Stoer-Wangner Algorithm)
题意:求全局最小割
不能用网络流求最小割,枚举举汇点要O(n),最短增广路最大流算法求最大流是O(n2m)复杂度,在复杂网络中O(m)=O(n2),算法总复杂度就是O(n5);就算你用其他求最大流的算法,算法总复杂度也要O(n4)。所以用网络流算法求解最小割集复杂度不会低于O(n4)。所以就要用Stoer_Wagner算法。算法复杂度为O(n3)。如果加堆优化,复杂度会降为O(n2logn)。
Stoer_Wagner算法:
Stoer_Wagner算法是求无向图全局最小割的一个有效算法,最坏时间复杂度O(n3),主要思想是先找任意2点的最小割,然后记录下这个最小割,再合并这2个点。这样经过n−1次寻找任意2点最小割,每次更新全局最小割。最后整张图缩成一个点,算法结束,所保存下来的最小割就是全局最小割。
具体步骤:
如果G的最小割Cut把G分成M,N两个点集,那么枚举的s和t无非以下两种情况:
①:如果s∈M,t∈N则Min-C(s,t) = Cut(最终的最小割)
②:如果s,t∈M(或s,t∈N)则Min-C (s,t) >= Cut(合并s和t后并不影响最小割,所以就把st合并了得到中间结果图G',继续在图G'上求最小割直到命中条件①)
因为利用到了中间结果G',G'<G,所以降低了复杂度。另外,这个合并的思想有点像Prim最小生成树算法,prim维护的dis数组的含义是集合A外的点到到集合树的最短边权,而Stoer−Wagner维护的w数组是集合A外的点,到集合A所有点的边权之和【割集】。
至于②中提到的合并操作,定义为Contract(s,t) := 删掉点 s, t 及边(s, t),加入新节点 c,对于任意 v ∈ 与s,t联通的点集,做一条新的边w(v, c) = w(c, v) = w(s, v) + w(t, v)
1. 设最小割cut=INF, 任选一个点s到集合A中, 定义W(A, p)为A中的所有点到A外一点p的权值总和. 2. 对刚才选定的s, 更新W(A,p)(该值递增). 3. 选出A外一点p, 且W(A,p)最大的作为新的s, 若A!=|V|, 则继续2. //最大割
4. 把最后进入A的两点记为s和t, 用W(A,t)更新cut.
5. 合并st,即新建顶点u, 边权w(u, v)=w(s, v)+w(t, v), 删除顶点s和t, 以及与它们相连的边
6. 若|V|!=1则继续1.
Stoer−Wagner的正确性:
设S和T是图G的2个顶点,图G的全局最小割要么是S−T的最小割,此时S和T在G的全局最小割的2个不同的子集中,要么就是G中将S和T合并得的的新图G′的全局最小割,此时S和T在G的全局最小割的同一子集中。
合并后对边权进行调整对全局最小割没有任何影响。所以只需要不断求出当前图中任意2个点的最小割,然后合并这2个点。不断缩小图的规模求得最小割。
5,6合并成
1 #include <cstdio> 2 #include <cstring> 3 #include <algorithm> 4 5 using namespace std; 6 7 int e[510][510]; //边权 8 int w[510]; //各点与A集合中所有点的边权之和, w(A,x)=∑w(id[i],x) id[i]∈A 9 int id[510]; //顶点的重新索引,由于合并顶点的需要 10 bool vis[510]; //顶点是否已访问 11 12 int main() 13 { 14 int n,m; 15 while(scanf("%d%d",&n,&m)!=EOF) 16 { 17 memset(e,0, sizeof(e)); 18 for(int i=0;i<n;i++)id[i]=i;//未合并之前,各顶点索引自己 19 for(int i=0;i<m;i++) 20 { 21 int u,v,c; 22 scanf("%d%d%d",&u,&v,&c); 23 e[u][v]+=c; 24 e[v][u]+=c;//无向图 25 } 26 27 int ans=0x3f3f3f3f;//全局最小割 28 while(n>1) 29 { 30 memset(vis,0, sizeof(vis)); 31 memset(w,0, sizeof(w)); 32 int s,t=0;//最后两个顶点 33 vis[0]=1;//默认第一个顶点入集合A 34 for(int i=1;i<n;i++) 35 { 36 s=t; 37 t=-1; 38 for(int j=1;j<n;j++) 39 { 40 if(!vis[j]) 41 { 42 w[j]+=e[id[j]][id[s]]; 43 if(t==-1||w[j]>w[t])t=j;//找最大的割集 44 } 45 } 46 vis[t]=1; //加入集合A 47 } 48 ans=min(ans,w[t]); 49 for(int i=0;i<n;i++) // 合并s,t为s点 50 { 51 if(i==s||i==t)continue; 52 e[id[i]][id[s]]+=e[id[i]][id[t]]; 53 e[id[s]][id[i]]+=e[id[i]][id[t]]; //边权w(s, v)=w(s, v)+w(t, v) 54 } 55 id[t]=id[--n]; // 赋值顶点n-1从而删除t点, 顶点数量-1 56 } 57 printf("%d\n",ans); 58 } 59 return 0; 60 }
参考自:http://www.hankcs.com,yogykwan和QAQqwe
上一篇: 梳理与总结:大流量/小割值算法详解
下一篇: 求解无向图的最小分割/整体最小划分