图上的文章续(KM算法)

KM算法只适用于完全二分图

KM算法是神马啊:

对于具有二部划分( V1, V2 )的加权完全二分图,其中 V1= { x1, x2, x3, … , xn }, V2= { y1, y2, y3, … , yn },边< xi, yj >具有权值 Wi,j 。该带权二分图中一个总权值最大的完美匹配,称之为最佳匹配。

诶。。。什么是完美匹配呢

二分图的最大匹配,完美匹配

好了
现在我们该知道的都知道了,
为了方便叙述,以下都把二分图的两个部分称为X,Y

Kuhn-Munkres 算法(KM算法,也称匈牙利算法)
为了学习这个算法,我们引入两个定义

可行顶标
一个节点函数,使得对于任意弧(x,y),有l(x)+l(y)>=w(x,y)
相等子图
原图的生成子图,包含所有点,但只包含满足l(x)+l(y)=w(x,y)的所有边(x,y)。

关于这两个概念,有一个极为重要的定理:

如果相等子图有完美匹配,则该匹配是原图的最大权匹配

证明就不说了(博主看不懂)

也就是说我们现在的问题就是找到合理的可行顶标,得到能够完美匹配的相等子图

算法的思路是这样的,
我们先随意确定一个顶标(一般的,X的顶标是ta所发出的边的最大值,Y部的顶标是0)
然后求相等子图的最大匹配,如果存在完美匹配,算法终止

如果不存在,我们就要修改顶标
我们现在的问题是:要怎么修改顶标呢?
入门手册上bulabula了一大堆关于匈牙利树的知识,然而我并没有看懂,所以引入一个简单的例子:

举例

现在你在一个相亲现场,
作为主办方,你当然是希望能牵手的越多越好
现场有n男n女,有些男生和女生之间互有好感
当然女生对不同男生的好感度是不一样的
这些关系会场员工给我们抽象成了这样的一张图:
这里写图片描述
你希望大家都能尽量满意,所以你想要计算出来一种最好的配对方式,这样就可以暗箱操作一下了

首先,每一个女生都会有一个期望值(当然是和最喜欢的人在一起最好了)
那么期望值就是与ta都有好感度的男生中的最大值
男生就比较随便了,只要有人要ta的就行
这里写图片描述

接下来,我们开始配对了
我们遵循这样的原则:

原则一:
从第一个女生开始,选择一个男生,使得两人的期望和要和两人之间的好感度一样

注意:每一轮匹配,每个男生只会被尝试匹配一次!

第一轮

女一的选择
女1—男1 (4+0!=3,pass)
女1—男3 (4+0==4,OK)
所以女1和男3配对

女一的选择
女2—男1 (3+0!=2,pass)
女2—男2 (3+0!=1,pass)
女2—男3 (3+0==3,wait。。。)
所以女2按理说应该和男3在一起的
但是男3已经有女朋友了(女1)而且女1没有别的选择了
(我们不考虑渣男)

解决配对失败
这一轮中,参与匹配的有女1,女2,男3
怎么办?
只能让女生降低一下自己的标准了,但是降低多少呢
总不能降成负数吧。。。

原则二:
任意一个参与匹配女生能换到任意一个这轮没有被选择过的男生所需要降低的最小值

如果说:女1选择男1,期望值要降低1。 女2选择男1,期望值要降低1。 女2选择男2,期望值要降低2。
于是,只要期望值降低1,就有妹子可能选择其他人。

那么这轮参与的所有妹子的期望就要降低1
同时,男3由于有两个女人为ta争风吃醋,就骄傲了(男的就是这么肤浅)
于是ta的期望提高了1(同妹子们降低的期望值相同)

现在的情况是这样的:
这里写图片描述
男3和女1在一起了
女2找不到男朋友了,
于是大家降低了标准之后,
女2就继续尝试了(这时男3和女1还没有分手):
根据原则一,女2和男1在一起了

第二轮

女3的选择
然而女3这时候只能形影相吊了。。。

解决配对失败
这次参与配对的只有女3
如果女3的标准降低1,ta就可以和男3在一起了

第三轮

女3相中了男3
然而男3和女1在一起了
但是这个时候女1还有别的选择
女1—-男1
然而男1本来是和女2在一起的
女2考虑换男朋友
女2—男3
女3的选择
终于轮到女3了
又没人了。。。

解决配对失败
这一轮中我们提到了:女1,女2,女3,男1,男3
(男2真tomato惨)
根据原则二,标准需要下降1(男生要对应上升):
这里写图片描述

第四轮

当前状态是
女1—男3
女2—男1
女3相中男3
女1考虑换男朋友:女1—男1
女2考虑换男朋友:女2—男2
这样女3成功和男3在一起了

匹配成功!!!撒花~~

实际上眼明人应该能够隐隐约约感受出来这是一种递归的过程

下面给出代码:

int W[N][N],n;
int Lx[N],Ly[N];   //顶标,就是每个人的标准
int Y[N];   //Y部第i个点在X部中的配对点编号 
bool L[N],R[N];   //X/Y部第i个点是否有标记

int match(int i)   //二分图匹配 
{
    L[i]=1;
    for (int j=1;j<=n;j++)
        if (Lx[i]+Ly[j]==W[i][j]&&!R[j])  //可以配对   
        {
            R[j]=1;
            if (!Y[j]||match(Y[j]))   //j没有配对或者可以经过更换满足条件
            {
                Y[j]=i;
                return 1;
            } 
        }
    return 0;
}

void change()   //修改顶标 
{
    int a=1<<30;
    for (int i=1;i<=n;i++)
        if (L[i])  //i参加了这一轮配对 
        for (int j=1;j<=n;j++)
            if (!R[j])   //j没有参加
               a=min(a,Lx[i]+Ly[j]-W[i][j]);    //寻找新顶标 
    for (int i=1;i<=n;i++)
    {
        if (L[i]) Lx[i]-=a;   //参加了配对的才修改顶标 
        if (R[i]) Ly[i]+=a;
    }            
}

void KM()
{
    for (int i=1;i<=n;i++)
    {
        Y[i]=Lx[i]=Ly[i]=0;
        for (int j=1;j<=n;j++)
            Lx[i]=max(Lx[i],W[i][j]);   //找到顶标,W边权 
    }
    for (int i=1;i<=n;i++)   //相当于女生一个一个找男朋友 
        for (;;)
        {
            for (int j=1;j<=n;j++) L[i]=R[i]=0;
            if (match(i)) break;   //存在完美匹配 
            else change();
        } 
} 

注意Y和R两个数组不是一回事,
要分别判断和处理

再给出一种N^3的写法
唯一的区别就是我们另开了一个数组记录顶标的改变量
这样就不用N^2寻找新顶标了

int match(int i)
{
    L[i]=1;
    for (int j=1;j<=m;j++)
        if (!R[j])   //Y部每个点只匹配一遍 
        {
            int v=Lx[i]+Ly[j]-W[i][j];
            if (!v)
            {
                R[j]=1;  //参与匹配的标记 
                if (!belong[j]||match(belong[j]))
                {
                    belong[j]=i;
                    return 1;
                }
            }
            else slack[j]=min(v,slack[j]);   //记录一下如果需要修改,顶标的改变量 
        }
    return 0;
}

int KM()   //X部元素个数:n,Y部元素个数:m 
{
    memset(belong,0,sizeof(belong));   //Y部在X部的匹配元素 
    for (int i=1;i<=n;i++)   //顶标 
    {
        Lx[i]=W[i][1];  //防止有负边权 
        Ly[i]=0;  
        for (int j=2;j<=m;j++) Lx[i]=max(Lx[i],W[i][j]);
    }
    for (int i=1;i<=n;i++)
    {
        for (int j=1;j<=m;j++) slack[j]=INF;   //(德尔塔)顶标 
        while (1)
        {
            memset(L,0,sizeof(L));
            memset(R,0,sizeof(R));
            if (match(i)) break;
            int a=INF;
            for (int j=1;j<=m;j++)
                if (!R[j]) a=min(a,slack[j]);   //寻找新顶标,新顶标只与没有参与匹配的Y点有关 
            for (int j=1;j<=n;j++)
                if (L[i]) Lx[i]-=a;
            for (int j=1;j<=m;j++)
                if (R[j]) Ly[j]+=a; 
                else slack[j]-=a;    //
        }
    }
    int ans=0;
    for (int i=1;i<=m;i++) ans+=W[belong[i]][i];
    return ans;
}

转载于:https://www.cnblogs.com/wutongtong3117/p/7673116.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值