KM算法

 

  原文转载自大牛,略有改动

  

  KM算法是用来求完备匹配下的最大权匹配: 在一个二分图内,左顶点为X,右顶点为Y,现对于每组左右连接<Xi,Yj>有权Wij,求一种匹配使得所有Wij的和最大-------即最佳匹配。

 

  记 L(x) 表示结点 x 的标记量,如果对于二部图中的任何边<x,y>,都有 L(x)+ L(y)>= Wx,y,我们称 L 为二部图的可行顶标。

   设 G(V,E) 为二部图, G'(V,E') 为二部图的子图。如果对于 G' 中的任何边<x,y> 满足, L(x)+ L(y)== Wx,y,我们称 G'(V,E') 为 G(V,E) 的等价子图。
  
  定理一:设 L 是二部图 G 的可行顶标。若 L的等价子图 G有完美匹配 M,则 M 是 G 的最佳匹配。
 
   由上述定理,我们可以通过来不断修改可行顶标,得到等价子图,从而求出最佳匹配。

 

 
  基本概念
 
   对于二分图,约定每个点都设有一个标号, 记lx[i]为X方点 i 的标号,ly[j]为Y方点 j 的标号。
 
   可行点标:对于图中任意边(i,j,w)都有lx[i]+ly[j]>=W,则这一组点标是可行点标。
   可行边:特别的,对于任意 lx[i]+ly[j]==W 的边(i,j,w)称为可行边。
 
  有了以上概念,我们可以知道KM算法的核心思想就是,不断的修改某些点的标号(满足点标始终可行),增加图中可行边数量,直到图中存在仅有可行边组成的完全匹配为止。
  此时得到完全匹配匹配一定是最佳匹配:因为由可行点标的定义,图中任意一个完全匹配,其边权总和均不大于其相应点标号之和,而仅有可行边组成的完全匹配的边权总和等于其相应点的标号之和。
 
 
  Kuhn-Munkras算法大致流程:
  (1)初始化可行顶标的值;
  (2)用匈牙利算法寻找完备匹配;
  (3)若未找到完备匹配则修改可行顶标的值;
  (4)重复(2)(3)直到找到相等子图的完备匹配为止;
 
 

 

  KM算法及其具体过程

  对于以上KM算法的大致步骤,我们在详细叙述它的具体步骤:
  

  ① 初始化可行顶标:

    x[i]=max{e.W|e.x=i};即每个X方点的初始标号为与这个X方点相关联的权值最大的边的权值。

    ly[j]=0;即每个Y方点的初始标号为0。

  这个初始点标显然是可行的,并且,与任意一个X方点关联的边中至少有一条可行边
  ② 从每个X方点开始DFS增广。DFS增广的过程与最大匹配的Hungary算法基本相同,只是要注意两点:一是只找可行边,二是要把搜索过程中遍历到的X方的点全部记下来进行后面的修改;

  ③ 若增广成功,则从下一点继续增广。

  ④ 得到 d 值,修改可行线标后继续增广。

  以上就是整个算法的全部步骤。


  对于第二步,增广的结果有两种:若成功,则该点增广完成,进入下一个点的增广。若失败,则需要改变一些点的标号,使得图中可行边的数量增加。

  方法为:将所有在增广轨中(就是在增广过程中遍历到)的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值即可。这样一方面可以保证点标的可行性,另一方面,经过这一步后,图中至少会增加一条可行边。

  

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

【求二分图的最小权匹配】
只需把权值取反,变为负的,再用KM算出最大权匹配,取反则为其最小权匹配。

 

hdu 2255

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 #define _Clr(x, y) memset(x, y, sizeof(x))
 5 #define INF 0x3f3f3f3f
 6 #define N 310
 7 using namespace std;
 8 
 9 int map[N][N], match[N];
10 int lx[N], ly[N];
11 int slack[N], n;
12 bool used_x[N], used_y[N];
13 
14 bool dfs(int x)
15 {
16     used_x[x] = true;
17     for(int i=1; i<=n; i++)
18     {
19         if(used_y[i]) continue;
20         int tp = lx[x]+ly[i]-map[x][i];
21         if(tp==0) // 在相等子图中:<x, i>为可行边
22         {
23             used_y[i] = true;
24             if(match[i]==-1 || dfs(match[i]))
25             {
26                 match[i] = x;
27                 return true;
28             }
29         }
30         else
31             slack[i] = min(slack[i], tp);
32     }
33     return false;
34 }
35 
36 int KM()
37 {
38     // 初始化可行项标
39     _Clr(ly, 0);
40     _Clr(match, -1);
41     for(int i=1, j=1; i<=n; i++)
42         for(j=1, lx[i]=-INF; j<=n; j++)
43             lx[i] = max(lx[i], map[i][j]);
44 
45     for(int i=1; i<=n; i++)
46     {
47         _Clr(slack, INF);  // 初始化Y方点的松弛量函数
48         while(1)
49         {
50             _Clr(used_x, 0);
51             _Clr(used_y, 0);
52             if(dfs(i)) break; // 找到增广路,找下一条。
53             
54             // 获取d值:不在增广轨中的Y方的松弛量slack[]的最小值
55             int d = INF;
56             for(int j=1; j<=n; j++)
57                 if(!used_y[j] && slack[j] < d)
58                     d = slack[j];
59 
60             // 在增广轨中的 X 方点的lx[]减去d值
61             for(int j=1; j<=n; j++)
62                 if(used_x[j]) lx[j] -= d;
63 
64             // 在增广轨中的 Y 方点的ly[]加上d值,
65             // 同时不再增广轨中的 Y 方点松弛量减去d值
66             for(int j=1; j<=n; j++)
67                 if(!used_y[j])
68                     slack[j] -= d;
69                 else ly[j] += d;
70         }
71     }
72     int ans=0;
73     for(int i=1; i<=n; i++)
74         if(match[i] > -1) ans += map[match[i]][i];
75     return ans;
76 }
77 
78 int main()
79 {
80     while(~scanf("%d", &n))
81     {
82         for(int i=1; i<=n; i++)
83             for(int j=1; j<=n; j++)
84                 scanf("%d", &map[i][j]);
85         printf("%d\n", KM());
86     }
87     return 0;
88 }
View Code

 

现 在有1所学校,N个学生,M个公寓,还有一个R值,代表学生对该公寓的满意度,数越高表示越喜欢住在该所公寓,0表示不喜欢也不讨厌,如果为负数则代表不 喜欢住,也不能住。现在校长想让所有学生的满意度最高,而且学生跟公寓是一一对应的,另外,学生也不能入住那些没对公寓进行评价的。
求出最大值。
 
这个有几个需要的注意的地方:
1. N,M可能不相等,所以要得到N的完备匹配,N<=M才行。
2. 图中有负权值的边。
3. 判断完备匹配是否存在。
 
对于第二点,因为边为负表示不会入住,所以我们可以初始化mat[][]为-INF。然后在输入边的时候忽略掉负边。在匹配的时候统计合法匹配的个数并忽略掉初始边即可。
 
 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 #define _Clr(x, y) memset(x, y, sizeof(x))
 5 #define INF 0xfffffff
 6 #define N 520
 7 using namespace std;
 8 
 9 int mat[N][N], match[N];
10 int slack[N], n, m;
11 int lx[N], ly[N];
12 bool used_x[N], used_y[N];
13 
14 bool dfs(int x)
15 {
16     used_x[x] = true;
17     for(int i=0; i<m; i++)
18     {
19         if(used_y[i]) continue;
20         int t = lx[x] + ly[i] - mat[x][i];
21         if(t==0)
22         {
23             used_y[i] = true;
24             if(match[i]==-1 || dfs(match[i]))
25             {
26                 match[i] = x;
27                 return true;
28             }
29         }
30         else slack[i] = min(slack[i], t);
31     }
32     return false;
33 }
34 
35 void KM()
36 {
37     _Clr(ly, 0);
38     _Clr(match, -1);
39     for(int i=0, j; i<n; i++)
40     for(lx[i]=-INF,j=0; j<m; j++)
41         lx[i] = max(lx[i], mat[i][j]);
42     for(int x=0; x<n; x++)
43     {
44         for(int i=0; i<m; i++) slack[i] = INF;
45         while(1)
46         {
47             _Clr(used_x, 0);
48             _Clr(used_y, 0);
49             if(dfs(x)) break;
50 
51             int d = INF;
52             for(int i=0; i<m; i++)
53                 if(!used_y[i] && slack[i] < d)
54                     d = slack[i];
55             if(d==INF)
56             {
57                 puts("-1");
58                 return;
59             }
60             for(int i=0; i<n; i++)
61                 if(used_x[i]) lx[i] -= d;
62             for(int i=0; i<m; i++)
63                 if(used_y[i]) ly[i] += d;
64                 else slack[i] -= d;
65         }
66     }
67     int cnt=0, ans=0;
68     for(int i=0;i<m; i++)
69         if(match[i]!=-1 && mat[match[i]][i]!=-INF)
70             ans += mat[match[i]][i], cnt++;
71     if(cnt==n)
72         printf("%d\n", ans);
73     else puts("-1");
74 }
75 int main()
76 {
77     int s, r, v, k;
78     int lop=0;
79     while(~scanf("%d%d%d", &n, &m, &k))
80     {
81         for(int i=0; i<n; i++)
82         for(int j=0; j<m; j++) mat[i][j] = -INF;
83         for(int i=0; i<k; i++)
84         {
85             scanf("%d%d%d", &s, &r, &v);
86             if(v >= 0)
87                 mat[s][r] = v;
88         }
89         printf("Case %d: ", ++lop);
90         if(n>m)
91             puts("-1");
92         else
93             KM();
94     }
95     return 0;
96 }
View Code

 

转载于:https://www.cnblogs.com/khan724/p/4376086.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值