这个算法的本质还是不断的找增广路;

KM算法的正确性基于以下定理:
若由二分图中所有满足A[i]+B[j]=w[i,j]的边(i,j)构成的子图(称做相等子图)有完备匹配,那么这个完备匹配就是二分图的最大权匹配。

这个定理是显然的。因为对于二分图的任意一个匹配,如果它包含于相等子图,那么它的边权和等于所有顶点的顶标和;如果它有的边不包含于相等子图,那么它的边权和小于所有顶点的顶标和。所以相等子图的完备匹配一定是二分图的最大权匹配。

(1)可行点标:每个点有一个标号,记lx[i]为X方点i的标号,ly[j]为Y方点j的标号。如果对于图中的任意边(i, j, W)都有lx[i]+ly[j]>=W,则这一组点标是可行的。特别地,对于lx[i]+ly[j]=W的边(i, j, W),称为可行边

(2)KM算法的核心思想就是通过修改某些点的标号(但要满足点标始终是可行的),不断增加图中的可行边总数,直到图中存在仅由可行边组成的完全匹配为止,此时这个匹配一定是最佳的(因为由可行点标的的定义,图中的任意一个完全匹配,其边权总和均不大于所有点的标号之和,而仅由可行边组成的完全匹配的边权总和等于所有点的标号之和,故这个匹配是最佳的)。一开始,求出每个点的初始标号:lx[i]=max{e.W|e.x=i}(即每个X方点的初始标号为与这个X方点相关联的权值最大的边的权值),ly[j]=0(即每个Y方点的初始标号为0)。这个初始点标显然是可行的,并且,与任意一个X方点关联的边中至少有一条可行边

(3)然后,从每个X方点开始DFS增广。DFS增广的过程与最大匹配的Hungary算法基本相同,只是要注意两点:一是只找可行边,二是要把搜索过程中遍历到的X方点全部记下来(可以用vst搞一下),以进行后面的修改;

(4)增广的结果有两种:若成功(找到了增广轨),则该点增广完成,进入下一个点的增广。若失败(没有找到增广轨),则需要改变一些点的标号,使得图中可行边的数量增加。方法为:将所有在增广轨中(就是在增广过程中遍历到)的X方点的标号全部减去一个常数d,所有在增广轨中的Y方点的标号全部加上一个常数d,则对于图中的任意一条边(i, j, W)(i为X方点,j为Y方点):

<1>i和j都在增广轨中:此时边(i, j)的(lx[i]+ly[j])值不变,也就是这条边的可行性不变(原来是可行边则现在仍是,原来不是则现在仍不是);
<2>i在增广轨中而j不在:此时边(i, j)的(lx[i]+ly[j])的值减少了d,也就是原来这条边不是可行边(否则j就会被遍历到了),而现在可能是;
<3>j在增广轨中而i不在:此时边(i, j)的(lx[i]+ly[j])的值增加了d,也就是原来这条边不是可行边(若这条边是可行边,则在遍历到j时会紧接着执行DFS(i),此时i就会被遍历到),现在仍不是;
<4>i和j都不在增广轨中:此时边(i, j)的(lx[i]+ly[j])值不变,也就是这条边的可行性不变。
这样,在进行了这一步修改操作后,图中原来的可行边仍可行,而原来不可行的边现在则可能变为可行边。那么d的值应取多少?显然,整个点标不能失去可行性,也就是对于上述的第<2>类边,其lx[i]+ly[j]>=W这一性质不能被改变,故取所有第<2>类边的(lx[i]+ly[j]-W)的最小值作为d值即可。这样一方面可以保证点标的可行性,另一方面,经过这一步后,图中至少会增加一条可行边。

(5)修改后,继续对这个X方点DFS增广,若还失败则继续修改,直到成功为止;

以上就是KM算法的基本思路。但是朴素的实现方法,时间复杂度为O(n4)——需要找O(n)次增广路,每次增广最多需要修改O(n)次顶标,每次修改顶标时由于要枚举边来求d值,复杂度为O(n2)。实际上KM算法的复杂度是可以做到O(n3)的。我们给每个Y顶点一个“松弛量”函数slack,每次开始找增广路时初始化为无穷大。在寻找增广路的过程中,检查边(i,j)时,如果它不在相等子图中,则让slack[j]变成原值与lx[i]+ly[j]-w[i,j]的较小值。这样,在修改顶标时,取所有不在交错树中的Y顶点的slack值中的最小值作为d值即可。但还要注意一点:修改顶标后,要把所有的slack值都减去d。

模板:

  1. #include <stdio.h>
  2. #include <string.h>
  3. #include <iostream>
  4. #define maxn 310
  5. #define inf 100000000
  6. using namespace std;
  7.  
  8. int nx, ny;
  9. int link[maxn], lx[maxn], ly[maxn], slack[maxn]; //lx,ly为顶标,nx,ny分别为x点集y点集的个数
  10. int visx[maxn], visy[maxn], w[maxn][maxn];
  11.  
  12. int dfs(int x) {
  13. visx[x] = 1;
  14. for (int y=1; y<=ny; ++y) {
  15. if (visy[y])
  16. continue;
  17. int tmp = lx[x] + ly[y] - w[x][y];
  18. if (tmp == 0) {
  19. visy[y] = 1;
  20. if (link[y] == -1 || dfs(link[y])) {
  21. link[y] = x;
  22. return 1;
  23. }
  24. }
  25. else if (slack[y] > tmp) { // 不在相等子图中slack取最小的
  26. slack[y] = tmp;
  27. }
  28. }
  29. return 0;
  30. }
  31.  
  32. int km() {
  33. int i, j;
  34. memset(link, -1, sizeof(link));
  35. memset(ly, 0, sizeof(ly));
  36.  
  37. for (int i=1; i<=nx; ++i) { // lx初始化为左右相连边权值最大值
  38. lx[i] = -inf;
  39. for (int j=1; j<=ny; ++j) {
  40. if (w[i][j] > lx[i])
  41. lx[i] = w[i][j];
  42. }
  43. }
  44.  
  45. for (int x=1; x<=nx; ++x) {
  46. for (int i=1; i<=ny; ++i)
  47. slack[i] = inf;
  48. while(1) {
  49. //cout << "===\n";
  50. memset(visx, 0, sizeof(visx));
  51. memset(visy, 0, sizeof(visy));
  52.  
  53. if (dfs(x)) break; // 该点找到增广路 完成增广 进入下一个点的增广
  54. //若失败(没有找到增广轨),则需要改变一些点的标号,使得图中可行边的数量增加。
  55. //方法为:将所有在增广轨中(就是在增广过程中遍历到)的X方点的标号全部减去一个常数d,
  56. //所有在增广轨中的Y方点的标号全部加上一个常数d
  57.  
  58. int d = inf; // 找d
  59. for (int i=1; i<=ny; ++i) {
  60. if (!visy[i] && d>slack[i])
  61. d = slack[i];
  62. }
  63.  
  64. for (int i=1; i<=nx; ++i) {
  65. if (visx[i])
  66. lx[i] -= d;
  67. }
  68. for (int i=1; i<=ny; ++i) {
  69. if (visy[i])
  70. ly[i] += d;
  71. else slack[i] -= d;
  72. }
  73. }
  74. }
  75. int res = 0;
  76. for (int i=1; i<=ny; ++i) {
  77. if (link[i] != -1) {
  78. res += w[link[i]][i];
  79. }
  80. }
  81. return res;
  82. }

  

注意以下几点;

1,匹配两边节点不需要相等;

2,求最小权的时候只需要将权值取负,在求最大权即可;//十分有效

3,不存在的边权初始化为负无穷大;

自己又写了一遍,很多地方重新理解了一下。

  1. #include <stdio.h>
  2. #include <string.h>
  3. #include <iostream>
  4. #define siz 310
  5. #define inf 100000000
  6. using namespace std;
  7.  
  8. int lx[siz], ly[siz];
  9. int slack[siz];
  10. int visx[siz], visy[siz];
  11. int mp[siz][siz];
  12. int link[siz];
  13. int n, nx, ny;
  14.  
  15. bool dfs(int x) {
  16. visx[x] = 1; // 左集合访问到的点一定都在交错树中-------------
  17. for (int i=1; i<=ny; ++i) {
  18. if (visy[i]) continue;
  19. int w = lx[x] + ly[i] - mp[x][i];
  20. if (w == 0) {
  21. visy[i] = 1; // 右集合的点在相等子图中 就一定在交错树中------------
  22. if (link[i] == -1 || dfs(link[i])) {
  23. link[i] = x;
  24. return 1;
  25. }
  26. }
  27. else slack[i] = min(slack[i], w); // 修改不在相等子图中的点 的slack值
  28. }
  29. return 0;
  30. }
  31.  
  32. int km() {
  33. memset(link, -1, sizeof(link));
  34. // lx 和 ly的初始化
  35. memset(ly, 0, sizeof(ly));
  36. for (int i=1; i<=nx; ++i) {
  37. lx[i] = -inf;
  38. for (int j=1; j<=ny; ++j) {
  39. lx[i] = max(lx[i], mp[i][j]);
  40. }
  41. }
  42.  
  43. // 从x集合的每个点寻找增广路
  44. for (int x=1; x<=nx; ++x) {
  45. for (int i=1; i<=ny; ++i) { //slack 的初始化对当前点寻找增广路的左右过程中有效-----------
  46. slack[i] = inf;
  47. }
  48. while(1) {
  49. memset(visx, 0, sizeof(visx)); //每次寻找增广路之前初始化----------
  50. memset(visy, 0, sizeof(visy));
  51. if (dfs(x)) break; //该点增广完成
  52. // 否则修改可行顶标
  53. int d = inf;
  54. for (int i=1; i<=ny; ++i) {
  55. if (!visy[i])
  56. d = min(d, slack[i]);
  57. }
  58.  
  59. for (int i=1; i<=nx; ++i) {
  60. if (visx[i]) lx[i] -= d;
  61. }
  62. for (int i=1; i<=ny; ++i) {
  63. if (visy[i]) ly[i] += d;
  64. else slack[i] -= d;
  65. }
  66. }
  67. }
  68.  
  69. int res = 0;
  70. for (int i=1; i<=ny; ++i) { //必定已经找到一个所有相等子图的点导出的完美匹配---------
  71. if (link[i] != -1)
  72. res += mp[link[i]][i]; // 右集合i的匹配点link[i] 这里不像无向图那样,需要注意顺序---------
  73. }
  74. return res;
  75. }

  

推荐:

http://www.cppblog.com/MatoNo1/archive/2012/04/26/151724.html

http://blog.csdn.net/liguanxing/article/details/5665646

二分图 最大权匹配 km算法的更多相关文章

  1. HDU2255 奔小康赚大钱 —— 二分图最大权匹配 KM算法

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2255 奔小康赚大钱 Time Limit: 1000/1000 MS (Java/Others)    ...

  2. 二分图最大权匹配——KM算法

    前言 这东西虽然我早就学过了,但是最近才发现我以前学的是假的,心中感慨万千(雾),故作此篇. 简介 带权二分图:每条边都有权值的二分图 最大权匹配:使所选边权和最大的匹配 KM算法,全称Kuhn-Mu ...

  3. Hdu2255 奔小康赚大钱(二分图最大权匹配KM算法)

    奔小康赚大钱 Problem Description 传说在遥远的地方有一个非常富裕的村落,有一天,村长决定进行制度改革:重新分配房子. 这可是一件大事,关系到人民的住房问题啊.村里共有n间房间,刚好 ...

  4. HDU3488 Tour —— 二分图最大权匹配 KM算法

    题目链接:https://vjudge.net/problem/HDU-3488 Tour Time Limit: 3000/1000 MS (Java/Others)    Memory Limit ...

  5. Uvalive 4043 Ants —— 二分图最大权匹配 KM算法

    题目链接:https://vjudge.net/problem/UVALive-4043 题意: 给出n个白点和n个黑点的坐标, 要求用n条不相交的线段把他们连接起来,其中每条线段恰好连接一个白点和黑 ...

  6. hdu 2426 Interesting Housing Problem 最大权匹配KM算法

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2426 For any school, it is hard to find a feasible ac ...

  7. 网络流——二分图最优匹配KM算法

    前言 其实这个东西只是为了把网络流的内容凑齐而写的(反正我是没有看到过这样子的题不知道田忌赛马算不算) 算法过程 我们令左边的点(其实二分图没有什么左右)为女生,右边的点为男生,那么: 为每一个女生定 ...

  8. “亚信科技杯”南邮第七届大学生程序设计竞赛之网络预赛 A noj 2073 FFF [ 二分图最大权匹配 || 最大费用最大流 ]

    传送门 FFF 时间限制(普通/Java) : 1000 MS/ 3000 MS          运行内存限制 : 65536 KByte总提交 : 145            测试通过 : 13 ...

  9. @noi.ac - 507@ 二分图最大权匹配

    目录 @description@ @solution@ @accepted code@ @details@ @description@ 有一天你学了一个能解决二分图最大权匹配的算法,你决定将这个算法应 ...

随机推荐

  1. 数据库mysql中having 和where的区别

    having的用法 having字句可以让我们筛选成组后的各种数据,where字句在聚合前先筛选记录,也就是说作用在group by和having字句前.而 having子句在聚合后对组记录进行筛选. ...

  2. 浅谈Android手机木马手工查杀

    这篇文章主要是浅谈,所以会从简单方面开始讲起. 关于手机木马查杀,有些人会说安装手机杀毒软件不就解决了吗? 其实不然.因为手机和PC不一样,手机反木马技术没有PC端那么强. 就算你把目前市面上的所有手 ...

  3. 07 concurrency and Multi-version

    本章提要---------------------------------------------------------对并发和锁的进一步补充并发控制事务的隔离级别多版本控制读一致性的含义写一致性- ...

  4. Android alertdialog实现确认退出

    package com.example.alertdialog; import android.os.Bundle; import android.app.Activity; import andro ...

  5. Java中的JDBC数据库连接

    JDBC编程步骤 1.加载数据库驱动. // 加载驱动 Class.forName(driverClass) // 加载mysql驱动 Class.forName("com.mysql.jd ...

  6. MATLAB中的nargin与varargin的用法

    nargin的用法: nargin:number of function input arguments,指的是一个函数的输入变量的个数. 用法:nargin或着nargin(fx), 其中fx指的是 ...

  7. WPA/WPA2四次握手

    WPA/WPA2四次握手 官方文档:https://en.wikipedia.org/wiki/IEEE_802.11i-2004 The four-way handshake is designed ...

  8. Android 开源项目分类汇总(转)

    Android 开源项目分类汇总(转) ## 第一部分 个性化控件(View)主要介绍那些不错个性化的 View,包括 ListView.ActionBar.Menu.ViewPager.Galler ...

  9. opencl初探-sobel检测

    sobel检测的C版本,neon和GPU的时间比较. Platform: LG G3, Adreno 330 ,img size 3264x2448 sobel: C code neon GPU 73 ...

  10. iOS开发 判断字符串是不是表情

    + (BOOL)stringContainsEmoji:(NSString *)string { __block BOOL returnValue = NO; [string enumerateSub ...