KM算法

写完这一章,大概二分图的匹配问题也该完结了吧。 
其实我自己现在对KM算法也是刚刚理解,有理解不到位之处还请大家指出。 
KM算法:求在一个二分图的完备匹配中的最大权值匹配的算法。(下文简称为最佳完备匹配) 
似乎跟匈牙利算法有点相似, 
所以我们要引入一个基于匈牙利算法的一种算法,叫做KM算法。 
步骤如下: 
首先用邻接矩阵存储二分图,注意:如果只想求最大权值匹配而不要求是完备匹配的话,请把各个不相连的边的权值设置为0。 
之后进行下述步骤: 
1.运用贪心算法初始化标杆。 
2.运用匈牙利算法找到完备匹配。 
3.如果找不到,则通过修改标杆,增加一些边。 (这里增加的边是使的与最贪心匹配的值相差stc[i]),stc[y]=lx[x]+ly[y]-w[x][y] y是已经选择的顶点   x是未进入匹配序列的点,f数组只要初始化一次,但是对1到nx节点找增广路径时候都要初始化stc 再加入可匹配边后进行的匈牙利算法都要初始化一次vx vy 因为增广路径可能与上一次不同。当满足stc[y]=lx[x]+ly[y]-w[x][y]==0的边进行匈牙利算法。每一次扩展边到该顶点能找到增广路径为止。对于lx ly标程的修改是 左减右加(矛盾增广路径左边减去temp 右边加上temp 理论上总值是少了一个temp)temp是未匹配但是可匹配边的str最小值。修改过后就要修改str ,匹配过的y点的str数组值不用修改因为左减temp右加temp相当于没有变化,未匹配的y点stc[y]=stc[y]-temp;使的可以匹配的边数增加,使的该节点可以找到增广路径 到下一个节点

4.重复2,3的步骤,找到完备匹配时可结束。 

不得不说整个博文时间跨度到了一天…… 
继续写…… 
标杆分为X标杆和Y标杆,一般我们把比较少的点放在X标杆一侧。 
这样进行算法: 
首先要初始化两个标杆分别为X标杆和Y标杆,X标杆初始化为与之相连的最大边权,Y标杆初始化为0,且直接加入拥有最大边权的边。如果发现此时的匹配就是完备匹配,那么直接退出,否则进行标杆的更改。从第一个节点开始扫描,如果有合法的增广路,那么将其反选,扩充路径,如果该节点没有合法的增广路,那么则将增广路上的所有的X标杆上的点加入点集S,将Y标杆上的所有点加入点集T,从S和不在T集合中的点里面,计算d=min{L(x)+L(y)-w(x,y)};计算后,将在S点集内的x的顶标减d,在T的y的顶标加d。并将目前没有加入二分图的权值和等于顶标和的边作为未匹配边加入到二分图中,然后再在该节点寻找增广路,如果还是没有,则再次通过更改标杆来增加边,直到有增广路出现为止。之后重复寻找增广路的步骤以及更改标杆的步骤,如果出现了完备匹配,那么直接退出。 
我认为:求d的过程是把xy的顶标和及其权值相差最小的边加入到二分图中,而修改顶标的过程是使得其顶标之和等于新增入的边权,并使得之前选择的那些边仍然存在(即其顶标和仍等于权值和)。 

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#define INF 0x3f3f3f3f
using namespace std;
//stc[y]=lx[x]+ly[y]-w[x][y]  x代表的是已经选择的点 当lx[x]+ly[y]-w[x][y]为0时候代表x y这条边为可匹配边
//不为0时候代表x已选择 y没有选择
//其实个人感觉stc的维护就是首先选取最大的然后选取最小的差值来扩大可选择边,最后实现增广路的扩张
const int maxn=330;
int from[maxn],w[maxn][maxn];
int lx[maxn],ly[maxn],visx[maxn],visy[maxn],slack[maxn];
int nx,ny;
///定义slack[y]=min{lx[x]+ly[y]-w[x][y]},x已访问,y未访问
bool Find(int u)
{
    visx[u]=1;
    for(int v=0;v<ny;v++)if(!visy[v])
    {
        int tmp=lx[u]+ly[v]-w[u][v];
        if(tmp==0)
        {
            visy[v]=1;
            if(from[v]==-1 || Find(from[v]))
            {
                from[v]=u;
                return true;
            }
        }
        else slack[v]=min(slack[v],tmp);
    }
    return false;
}
int KM()
{
    memset(ly,0,sizeof(ly));
    for(int i=0;i<nx;i++)
    {
        lx[i]=-INF;
        for(int j=0;j<ny;j++)
            lx[i]=max(lx[i],w[i][j]);
    }
    memset(from,-1,sizeof(from));
    for(int u=0;u<nx;u++)
    {
        for(int i=0;i<ny;i++) slack[i]=INF;
        while(true)
        {
            memset(visx,0,sizeof(visx));
            memset(visy,0,sizeof(visy));
            if(Find(u)) break;
            int d=INF;
            for(int i=0;i<ny;i++)if(!visy[i])
                d=min(d,slack[i]);
            for(int i=0;i<nx;i++)if(visx[i])
                lx[i]-=d;
            for(int i=0;i<ny;i++)
            {
                if(visy[i]) ly[i]+=d;
                else slack[i]-=d;
            }
        }
    }
    int ans=0;
    for(int i=0;i<ny;i++)
        if(from[i]!=-1) ans+=w[from[i]][i];
    return ans;
}
int main()
{
    int n;
    while(~scanf("%d",&n))
    {
        nx=ny=n;
        for(int i=0;i<n;i++)
            for(int j=0;j<n;j++)
                scanf("%d",&w[i][j]);
        printf("%d\n",KM());
    }
    return 0;
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值