无向图最小割之stoer-wagner算法(知识点整理+板子总结)

思路来源

https://blog.csdn.net/DDelphine/article/details/77935670 详细图示

https://blog.csdn.net/u012317281/article/details/38115515 正确性证明

https://wenku.baidu.com/view/fdb484c3bb4cf7ec4afed08b.html 正确性证明

https://www.cnblogs.com/ylfdrib/archive/2010/08/17/1801784.html 板子整理

知识点整理

其实就是一个板子,不理解也罢,

但还是看了三个多小时来理解……

 

算法流程:

①随机选择一个点作为起始点,

类似最大生成树的计算过程,对每个不在树的距离进行更新,

这里是对wage数组进行更新,

即,对于不在树中的点u,要和当前生成树切断联系的代价为wage[u],

其实也就是,把u加入割点集所需消耗的代价,割点集两两不连通,

把代价大的割点取出来之后,剩下的就是代价小的,

每次选出最大的wage[u]加入树中,直到不在树中的点只剩两个s和t,

且如果下一步选点的话,应该选s,

说明t是wage[]值最小的,s是wage[]值次小的,

且由于t要切断与所有点的联系,所以wage[t]就是与t相连的边的条数

计算s和t的最小割,这是对于当前局面来讲,可割且最小的割

计算好答案之后,更新最小割的值,然后将s和t合并成一个点,

 

②重复①流程,至整个局面被合为一个点

由于每次类似prim的最大生成树的计算是O(n^2)

而每次合并一个点,最多合并n-1次,故复杂度O(n^3)

 

算法正确性证明:

记最小割把点集分成A、B两个部分,

①若s和t分属不同的点集,则s和t的割就是最小割,

这种情况下,点集A和点集B都已经被缩成一个点,s和t一边一个

 

②若s和t分属相同的点集,则s和t的割不是最小割,

所以将s和t合并,并不会影响最小割,且会使一侧的点集缩小

但实际运行过程中,在最小割未确定前无法确定是①还是②,故将每次的答案都记录取最小

 

下证,s和t的分法只有①和②两种情况,

不会出现如下图的情况,s和t间的割不是最小割,且s和t合并之后最小割没了


 

 

设第一次随机选的点在A中,

记A中点第一次扩展到B中的点u时,使u和树不连通的代价是wage[u],

由于u只切断了和A树的联系,如上图,故wage[u]就是最小割

①若A中任意点v的wage[v]>wage[u],则会更新完A中所有点,再去更新B中点,s和t都在B中

②若A中存在点v满足wage[v]<wage[u],则由于B中除u外任意点wage>wage[u],更新完B中点再回到A中,s和t都在A中

③反证法,设最后满足A中一个点,B中一个点成立,则wage[s]和wage[t]都小于wage[u],

这表明只割掉t的方案会使最小割小于wage[u],与最小割矛盾,故不成立

 

所以就只剩两种情况啦,

其实抄板子就好了,说了这么多还是不会敲代码……

板子整理

以poj2914 Minimum Cut为例

#include <iostream>
#include <cstdio>
#include <cstring>

using namespace std;
 
const int N=505;
const int INF=0x7fffffff;

//a[x][y] 代表x到y连的边数 即割 
//无向图 a[y][x]=a[x][y] 
//点的编号 1-n 
int n,m,u,v,w;
int a[N][N],dis[N];
bool vis[N],d[N]; 

int StoerWagner(int n)
{
    int p=n,ans=INF,mx,t,s,k,i;
    memset(d,0,sizeof d);
    while (--p>0)
    {
        memset(vis,0,sizeof vis);
        memset(dis,0,sizeof dis);
        i=1;
        while (d[i]) i++;
        vis[i]=1;
        for (int j=1;j<=n;j++)
            if(!d[j] && !vis[j])
            	dis[j]=a[i][j];
        t=s=i;
        for (;i<=n;i++)
        {
            mx=0;
            for (int j=1;j<=n;j++)
                if (!d[j] && !vis[j] && mx<dis[j])
                    mx=dis[k=j];
            if (!mx) break;
            vis[k]=1;
            for(int j=1;j<=n;j++)
                if(!d[j] && !vis[j])
                    dis[j]+=a[k][j];
            s=t;
            t=k;
        }
        if (ans>dis[t]) ans=dis[t];
		if (ans==0) return 0; 
        d[t]=1;
        for (int j=1;j<=n;j++)
            if (!d[j])
            {
                a[s][j]+=a[t][j];
                a[j][s]+=a[j][t];
            }
    }
    return ans;//返回无向图最小割 
}

int main()
{
	while(~scanf("%d%d",&n,&m))
	{
		memset(a,0,sizeof a);
		for(int i=1;i<=m;++i)
		{
			scanf("%d%d%d",&u,&v,&w);//输入是0到n-1的 
			u++;v++; 
			a[u][v]+=w;
			a[v][u]+=w;
		}
		printf("%d\n",StoerWagner(n));
	} 
 	return 0;
}

 

 

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Code92007

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值