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

 

你可能感兴趣的:(算法)