• 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
    ", 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
    ", 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
  • 相关阅读:
    SQL Server 服务由于登录失败而无法启动
    一个项目涉及到的50个Sql语句
    sql server2008 数据库镜像的问题
    C# 根据枚举名(string)获得enum中的枚举值
    在不同版本的 IIS 上使用 ASP.NET MVC(转)
    sqlite 默认当前时间
    Nginx反向代理后防盗链设置
    JavaScript 图片加载
    nginx配置文件中的location中文详解
    程序设计的十个做与不做
  • 原文地址:https://www.cnblogs.com/khan724/p/4376086.html
Copyright © 2020-2023  润新知