Stoer-Wagner算法
一个无向连通网络,去掉一个边集可以使其变成两个连通分量则这个边集就是割集;最小割集当然就权和最小的割集。
可以用最小切割最大流定理:
1.min=MAXINT,确定一个源点
2.枚举汇点
3.计算最大流,并确定当前源汇的最小割集,若比min小更新min
4.转到2直到枚举完毕
5.min即为所求输出min
不难看出复杂度很高:枚举汇点要O(n),最短增广路最大流算法求最大流是O((n^2)m)复杂度,在复杂网络中O(m)=O(n^2),算法总复杂度 就是O(n^5);哪怕采用最高标号预进流算法求最大流O((n^2)(m^0.5)),算法总复杂度也要O(n^4)
所以用网络流算法求解最小割集复杂度不会低于O(n^4)。
---------
prim算法不仅仅可以求最小生成树,也可以求“最大生成树”。最小割集Stoer-Wagner算法就是典型的应用实例。
求解最小割集普遍采用Stoer-Wagner算法,不提供此算法证明和代码,只提供算法思路:
1.min=MAXINT,固定一个顶点P
2.从点P用类似prim的s算法扩展出“最大生成树”,记录最后扩展的顶点和最后扩展的边
3.计算最后扩展到的顶点的切割值(即与此顶点相连的所有边权和),若比min小更新min
4.合并最后扩展的那条边的两个端点为一个顶点。
5.转到2,合并N-1次后结束
6.min即为所求,输出min
prim本身复杂度是O(n^2),合并n-1次,算法复杂度即为O(n^3)
如果在prim中加堆优化,复杂度会降为O((n^2)logn)
算法没太看懂,先学着敲一下。
下面是代码(模板):
#include<iostream>
#include<set>
//#include<map>
#include<vector>
#include<queue>
#include<cmath>
#include<climits>
#include<cstdio>
#include<string>
#include<cstring>
#include<algorithm>
typedef long long LL;
using namespace std;
const int MAX_N=505;
int N,M;
int mat[MAX_N][MAX_N];
int res;
void Stor()
{
int node[MAX_N],dis[MAX_N];
bool vis[MAX_N];
int prev;
for(int i=0;i<N;i++)
node[i]=i;
while(N>1)
{
int zj=1;
for(int i=1;i<N;i++)//初始化到已圈集合的割大小
{
dis[node[i]]=mat[node[0]][node[i]];
if(dis[node[i]]>dis[node[zj]])
zj=i;
}
prev=0;
memset(vis,0,sizeof(vis));
vis[node[0]]=true;
for(int i=1;i<N;i++)
{
if(i==N-1)//只剩最后一个没加入集合的点,更新最小割
{
res=min(res,dis[node[zj]]);
for (int k = 0; k <N; k++) //合并最后一个点以及推出它的集合中的点
mat[node[k]][node[prev]]=mat[node[prev]][node[k]]+= mat[node[k]][node[zj]];
node[zj] = node[--N]; //缩点后的图
}
vis[node[zj]]=true;
prev=zj;
zj=-1;
for(int j=1;j<N;j++)//将上次求的maxj加入集合,合并与它相邻的边到割集
{
if(!vis[node[j]])
{
dis[node[j]]+=mat[node[prev]][node[j]];
if(zj==-1||dis[node[zj]]<dis[node[j]])
zj=j;
}
}
}
}
cout<<res<<endl;
}
int main()
{
//freopen("in.txt","r",stdin);
int a,b,f;
while(cin>>N>>M)
{
memset(mat,0,sizeof(mat));
res=(1<<29);
for(int i=0;i<M;i++)
{
cin>>a>>b>>f;
mat[a][b]+=f;
mat[b][a]+=f;
}
Stor();
}
return 0;
}