带权二分图的最佳匹配(KM算法)

还是没看懂一般图都是最大匹配问题。。怪我太笨了哎~ 先来个看明白了的KM算法——寻找带权二分图的最佳匹配方法

一般对KM算法的描述,基本上可以概括成以下几个步骤:

(1) 初始化可行标杆
(2) 用匈牙利算法寻找完备匹配
(3) 若未找到完备匹配则修改可行标杆
(4) 重复(2)(3)直到找到相等子图的完备匹配

关于该算法的流程及实施,网上有很多介绍,基本上都是围绕可行标杆如何修改而进行的讨论,至于原理并没有给出深入的探讨。

KM算法是用于寻找带权二分图最佳匹配的算法。

二分图是这样一种图:所有顶点可以分成两个集:X和Y,其中X和Y中的任意两个在同一个集中的点都不相连,而来自X集的顶点与来自Y集的顶点有连线。当这些连线被赋于一定的权重时,这样的二分图便是带权二分图。

二分图匹配是指求出一组边,其中的顶点分别在两个集合中,且任意两条边都没有相同的顶点,这组边叫做二分图的匹配,而所能得到的最大的边的个数,叫做二分图的最大匹配。

我们也可以换个角度看二分图的最大匹配,即二分图的每条边的默认权重为1,我们求到的二分图的最大匹配的权重最大。对于带权二分图,其边有大于0的权重,找到一组匹配,使其权重最大,即为带权二分图的最佳匹配。

匈牙利算法一般用于寻找二分图的最大匹配。算法根据一定的规则选择二分图的边加入匹配子图中,其基本模式为:

初始化匹配子图为空
While 找得到增广路径
Do 把增广路径添加到匹配子图中

增广路径有如下特性:
1. 有奇数条边
2. 起点在二分图的X边,终点在二分图的Y边
3. 路径上的点一定是一个在X边,一个在Y边,交错出现。
4. 整条路径上没有重复的点
5. 起点和终点都是目前还没有配对的点,其他的点都已经出现在匹配子图中
6. 路径上的所有第奇数条边都是目前还没有进入目前的匹配子图的边,而所有第偶数条边都已经进入目前的匹配子图。奇数边比偶数边多一条边
7. 于是当我们把所有第奇数条边都加到匹配子图并把条偶数条边都删除,匹配数增加了1.

例如下图,蓝色的是当前的匹配子图,目前只有边x0y0,然后通过x1找到了增广路径:x1y0->y0x0->x0y2



其中第奇数第边x1y0和x0y2不在当前的匹配子图中,而第偶数条边x0y0在匹配子图中,通过添加x1y0和x0y2到匹配子图并删除x0y0,使得匹配数由1增加到了2。每找到一条增广路径,通过添加删除边,我们总是能使匹配数加1.

增广路径有两种寻径方法,一个是深搜,一个是宽搜。例如从x2出发寻找增广路径,如果是深搜,x2找到y0匹配,但发现y0已经被x1匹配了,于是就深入到x1,去为x1找新的匹配节点,结果发现x1没有其他的匹配节点,于是匹配失败,x2接着找y1,发现y1可以匹配,于是就找到了新的增广路径。如果是宽搜,x1找到y0节点的时候,由于不能马上得到一个合法的匹配,于是将它做为候选项放入队列中,并接着找y1,由于y1已经匹配,于是匹配成功返回了。相对来说,深搜要容易理解些,其栈可以由递归过程来维护,而宽搜则需要自己维护一个队列,并对一路过来的路线自己做标记,实现起来比较麻烦。

对于带权重的二分图来说,我们可以把它看成一个所有X集合的顶点到所有Y集合的顶点均有边的二分图(把原来没有的边添加入二分图,权重为0即可),也就是说它必定存在完备匹配(即其匹配数为min(|X|,|Y|))。为了使权重达到最大,我们实际上是通过贪心算法来选边,形成一个新的二分图(我们下面叫它二分子图好了),并在该二分图的基础上寻找最大匹配,当该最大匹配为完备匹配时,我们可以确定该匹配为最佳匹配。(在这里我们如此定义最大匹配:匹配边数最多的匹配和最佳匹配:匹配边的权重和最大的匹配。)

贪心算法总是将最优的边优先加入二分子图,该最优的边将对当前的匹配子图带来最大的贡献,贡献的衡量是通过标杆来实现的。下面我们将通过一个实例来解释这个过程。

有带权二分图:


算法把权重转换成标杆,X集跟Y集的每个顶点各有一个标杆值,初始情况下权重全部放在X集上。由于每个顶点都将至少会有一个匹配点,贪心算法必然优先选择该顶点上权重最大的边(最理想的情况下,这些边正好没有交点,于是我们自然得到了最佳匹配)。最初的二分子图为:(可以看到初始化时X标杆为该顶点上的最大权重,而Y标杆为0)


从X0找增广路径,找到X0Y4;从X1找不到增广路径,也就是说,必须往二分子图里边添加新的边,使得X1能找到它的匹配,同时使权重总和添加最大。由于X1通往Y4而Y4已经被X0匹配,所以有两种可能,一个是为X0找一个新的匹配点并把Y4让给X1,或者是为X1找一个新的匹配点,现在我们将要看到标杆的作用了。根据传统的算法描述,能够进入二分子图的边的条件为L(x)+L(y)>=weight(xy)。当找不到增广路径时,对于搜索过的路径上的XY点,设该路径上的X顶点集为S,Y顶点集为T,对所有在S中的点xi及不在T中的点yj,计算d=min{(L(xi)+L(yj)-weight(xiyj))},从S集中的X标杆中减去d,并将其加入到T集中的Y的标杆中,由于S集中的X标杆减少了,而不在T中的Y标杆不变,相当于这两个集合中的L(x)+L(y)变小了,也就是,有新的边可以加入二分子图了。从贪心选边的角度看,我们可以为X0选择新的边而抛弃原先的二分子图中的匹配边,也可以为X1选择新的边而抛弃原先的二分子图中的匹配边,因为我们不能同时选择X0Y4和X1Y4,因为这是一个不合法匹配,这个时候,d=min{(L(xi)+L(yj)-weight(xiyj))}的意义就在于,我们选择一条新的边,这条边将被加入匹配子图中使得匹配合法,选择这条边形成的匹配子图,将比原先的匹配子图加上这条非法边组成的非法匹配子图的权重和(如果它是合法的,它将是最大的)小最少,即权重最大了。好绕口的。用数学的方式表达,设原先的不合法匹配(它的权重最大,因为我们总是从权重最大的边找起的)的权重为W,新的合法匹配为W’,d为min{W-W’i}。在这个例子中,S={X0, X1},Y={Y4},求出最小值d=L(X1)+L(Y0)-weight(X1Y0)=2,得到新的二分子图:


重新为X1寻找增广路径,找到X1Y0,可以看到新的匹配子图的权重为9+6=15,比原先的不合法的匹配的权重9+8=17正好少d=2。
接下来从X2出发找不到增广路径,其走过的路径如蓝色的路线所示。形成的非法匹配子图:X0Y4,X1Y0及X2Y0的权重和为22。在这条路径上,只要为S={X0,X1,X2}中的任意一个顶点找到新的匹配,就可以解决这个问题,于是又开始求d。
d=L(X0)+L(Y2)-weight(X0Y2)=L(X2)+L(Y1)-weight(X2Y1)=1.
新的二分子图为:



重新为X2寻找增广路径,如果我们使用的是深搜,会得到路径:X2Y0->Y0X1->X1Y4->Y4X0->X0Y2,即奇数条边而删除偶数条边,新的匹配子图中由这几个顶点得到的新的权重为21;如果使用的是宽搜,会得到路径X2Y1,另上原先的两条匹配边,权重为21。假设我们使用的是宽搜,得到的新的匹配子图为:


接下来依次类推,直到为X4找到一个匹配点。

KM算法的最大特点在于利用标杆和权重来生成一个二分子图,在该二分子图上面找最大匹配,而且,当些仅当找到完备匹配,才能得到最佳匹配。标杆和权重的作用在于限制新边的加入,使得加入的新边总是能为子图添加匹配数,同时又令权重和得到最大的提高。


代码模板


#include <cstdio>
#include <string.h>
#include <vector>
#include <algorithm>
using namespace std;
int const MAX = 1000;
int const inf = 0x3fffffff;
int w[MAX][MAX];
int link[MAX];//代表当前与Y集合中配对的X集合中的点
int visx[MAX], visy[MAX];
int lx[MAX], ly[MAX];
int n, m;//代表X和Y中元素的个数

int can(int t)
{
    visx[t] = 1;
    for(int i = 1; i <= m; i++){
        if(!visy[i] && lx[t] + ly[i] == w[t][i]){//这里“lx[t]+ly[i]==w[t][i]”决定了这是在相等子图中找增广路的前提,非常重要
            visy[i] = 1;
            if(link[i] == -1 || can(link[i])){
                link[i] = t;
                return 1;
            }
        }
    }
    return 0;
}

int km()
{
    int sum = 0;
    memset(ly, 0, sizeof(ly));
    for(int i = 1; i <= n; i++){//把各个lx的值都设为当前w[i][j]的最大值
        lx[i] = -inf;
        for(int j = 1; j <= n; j++){
            if(lx[i] < w[i][j])
                lx[i] = w[i][j];
        }
    }
    memset(link, -1, sizeof(link));
    for(int i = 1; i <= n; i++){
        while(1){
            memset(visx, 0, sizeof(visx));
            memset(visy, 0, sizeof(visy));
            if(can(i))//如果它能够形成一条增广路径,那么就break
                break;
            int d = inf;//否则,后面应该加入新的边,这里应该先计算d值
            for(int j = 1; j <= n; j++)//对于搜索过的路径上的XY点,设该路径上的X顶点集为S,Y顶点集为T,对所有在S中的点xi及不在T中的点yj
                if(visx[j])
                    for(int k = 1; k <= m; k++)
                       if(!visy[k])
                            d = min(d, lx[j] + ly[k] - w[j][k]);
            if(d == inf)
            return -1;//找不到可以加入的边,返回失败(即找不到完美匹配)
            for (int j = 1; j <= n; j++)
                if (visx[j])
                    lx[j] -= d;
            for(int j = 1; j <= m; j++)
                if(visy[j])
                    ly[j] += d;
        }
    }
    for(int i = 1; i <= m; i++)
        if(link[i] > -1)
            sum += w[link[i]][i];
    return sum;
}

例题


1076. Trash

Time limit: 1.0 second
Memory limit: 64 MB
You were just hired as CEO of the local junkyard.One of your jobs is dealing with the incoming trash and sorting it for recycling.The trash comes every day in N containers and each of these containers contains certain amount of each of the N types of trash. Given the amount of trash in the containers find the optimal way to sort the trash. Sorting the trash means putting every type of trash in separate container. Each of the given containers has infinite capacity. The effort for moving one unit of trash from container i to j is 1 if i ≠  j otherwise it is 0.You are to minimize the total effort.

Input

The first line contains the number N (1 ≤  N ≤ 150), the rest of the input contains the descriptions of the containers.The (1 +  i)-th line contains the description of the i-th container the j-th amount (0 ≤ amount ≤ 100) on this line denotes the amount of the j-th type of trash in the i-th container.

Output

You should write the minimal effort that is required for sorting the trash.

Sample

inputoutput
4
62 41 86 94
73 58 11 12
69 93 89 88
81 40 69 13
650

问题分析:

     有n个垃圾桶,每个垃圾桶内有n种垃圾,现在是要你把垃圾分类,到每一个垃圾桶最后都只能装一种垃圾,从一个垃圾桶里把垃圾移到另一个垃圾桶会有所消耗,且都是单位消耗。。

     那么,要总消耗最少,就要原来每个垃圾桶各自保留的垃圾总和要最多,这么看来,问题就可以转化为带权二分图的最佳匹配问题。

      求出这个最佳匹配  k   最后 ans = s(所有垃圾总和)-k


代码


#include <cstdio>
#include <string.h>
#include <algorithm>
using namespace std;
const int MAX = 200;
const int inf = 0x3f3f3f3f;
int w[MAX][MAX];
int lx[MAX], ly[MAX];
int visx[MAX], visy[MAX];
int link[MAX];
int n;

int can(int t)
{
    visx[t] = 1;
    for(int i = 1; i <= n; i++){
        if(!visy[i] && lx[t] + ly[i] == w[t][i]){
            visy[i] = 1;
            if(link[i] == -1 || can(link[i])){
                link[i] = t;
                return 1;
            }
        }
    }
    return 0;
}

int km()
{
    int d;
    int sum = 0;
    memset(ly, 0, sizeof(ly));
    for(int i = 1; i <= n; i++){
        lx[i] = -inf;
        for(int j = 1; j <= n; j++)
            lx[i] = max(lx[i], w[i][j]);
    }
    memset(link, -1, sizeof(link));
    for(int i = 1; i <= n; i++){
        while(1){
            memset(visx, 0, sizeof(visx));
            memset(visy, 0, sizeof(visy));
            if(can(i))  break;
            for(int j = 1; j <= n; j++)
                if(visx[j])
                    for(int k = 1; k <= n; k++)
                        if(!visy[k])
                            d = min(d, lx[j] + ly[k] - w[j][k]);
            if(d == inf)
                return -1;
            for(int j = 1; j <= n; j++)
                if(visx[j])
                    lx[j] -= d;
            for(int j = 1; j <= n; j++)
                if(visy[j])
                    ly[j] += d;
        }
    }
    for(int i = 1; i <= n; i++)
        if(link[i] > -1)
            sum += w[link[i]][i];
    return sum;
}

int main()
{
    int ans = 0;
    scanf("%d", &n);
    for(int i = 1; i <= n; i++)
        for(int j = 1; j <= n; j++){
            scanf("%d", &w[i][j]);
            ans += w[i][j];
        }
    ans -= km();
    printf("%d\n", ans);
    return 0;
}







  • 13
    点赞
  • 58
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
KM算法,全称Kuhn-Munkres算法,是一种用于求解二分图最佳匹配的算法。它可以找到一个匹配,使得两个集合内的所有顶点能够一一匹配,并且获得的权值最大或最小。KM算法在求解带权二分图匹配时,融合了匈牙利算法的思想。算法的步骤如下: 1. 初始化:将两个集合内的顶点分别标记为未被匹配状态。 2. 根据特定的规则,遍历第一个集合内的顶点。 3. 对于每个选中的顶点,遍历第二个集合内的顶点,找到与其相连的较优边。较优边的选择可以根据具体情况而定,可以是较大的权值或者较小的权值。 4. 如果找到了满足条件的边,判断该边对应的第二个顶点是否已经被匹配。如果该顶点还未被匹配,则直接将其与第一个顶点进行匹配。 5. 如果该顶点已经被匹配,但是与其匹配的顶点还可以找到其他的可匹配顶点,则将该顶点重新匹配给第一个顶点。 6. 循环执行步骤2-5,直到无法找到满足条件的边。 通过这样的循环匹配KM算法能够找到二分图最佳匹配。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [二分图的完全匹配---KM算法](https://blog.csdn.net/li13168690086/article/details/81557890)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值