二分图 KM算法(求二分图带权值的最大匹配)

来源:互联网 发布:pps网络电视在线观看 编辑:程序博客网 时间:2024/06/14 20:44
先说KM算法求二分图的最佳匹配思想,再详讲KM的实现。
【KM算法求二分图的最佳匹配思想】
对于具有二部划分( V1, V2 )的加权完全二分图,其中 V1= { x1, x2, x3, ... , xn }, V2= { y1, y2, y3, ... , yn },边< xi, yj >具有权值 Wi,j 。该带权二分图中一个总权值最大的完美匹配,称之为最佳匹配。
 
记 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 等价子图 GL 有完美匹配 M,则 M 是 G 的最佳匹配。
证明:由于 GL 是 G 的等价子图,M 是 GL 的完美匹配,所以,M 也是 G  的完美匹配。以由于对于匹配 M 的每条边 e ,都有 e∈ E( GL ),而且 M 中每条边覆盖每个顶点正好一次,所以
W( M )= å W(e), e∈ M = å L(x), x∈ V
另一方面,对于 G 的任何完美匹配 M' 有
W( M' )= å W(e), e∈ M' <= å L(x), x∈ V
于是 W( M )>= W( M' ),即 M 是 G 的最优匹配。
 
由上述定理,我们可以通过来不断修改可行顶标,得到等价子图,从而求出最佳匹配。
就像匈牙利算法一样,我们依次为每一个顶点 i 寻找增广路径,如果寻找增广路径失败,我们就修改相应的可行顶标,来得到增广路径。
如图:
|  1  2  3  |
|  3  2  4  |
|  2  3  5  |
若要对这个完全二分图求最佳匹配
 
初始化:
Lx(1)= max{ y| w(1,y), 1<= y<= 3 }= max{ 1, 2, 3 }= 3, Ly(1)= 0
Lx(2)= max{ 3, 2, 4 }= 4, Ly(2)= 0
Lx(3)= max{ 2, 3, 5 }= 5, Ly(3)= 0;
我们建立等价子图( 满足 Lx(x)+ Ly(y)== W(x,y) ) 如下:
 km算法求二分图最佳匹配
对于该图,运用匈牙利算法对 X 部顶点 1 求增广路径,得到一个匹配,如图( 红色代表匹配边 ):km算法求二分图最佳匹配
 对 X 部顶点 2 求增广路径失败,寻找增广路径的过程为 X 2-> Y 3-> X 1。我们把寻找增广路径失败的 DFS 的交错树中,在 X 部顶点集称之为 S, 在 Y 部的顶点集称之为 T。则 S= { 1, 2 },T= { 3 }。现在我们就通过修改顶标值来扩大等价子图,如何修改。
 
1)   我们寻找一个 d 值,使得 d= min{ (x,y)| Lx(x)+ Ly(y)- W(x,y), x∈ S, y∉ T },因些,这时 d= min{
Lx(1)+Ly(1)-W(1,1),  Lx(1)+Ly(2)-W(1,2),  Lx(2)+Ly(1)-W(2,1),  Lx(2)+Ly(2)-W(2,2) }=
min{ 3+0- 1, 3+0-2,  4+0-3,  4+0-2 }= min{ 2, 1, 1, 2 }= 1。
寻找最小的 d 是为了保证修改后仍满足性质对于边 <x,y> 有 Lx(x)+ Ly(y)>= W(x,y)。
 
2)   然后对于顶点 x
1. 如果 x∈ S 则 Lx(x)= Lx(x)- d。
2. 如果 x∈ T 则 Ly(x)= Ly(x)+ d。
3. 其它情况保持不变。
如此修改后,我们发现对于边<x,y>,顶标 Lx(x)+ Ly(y) 的值为
1.  Lx(x)- d+ Ly(y)+ d,  x∈ S, y∈ T。
2.  Lx(x)+ Ly(y),  x∉ S,  y∉ T。
3.  Lx(x)- d+ Ly(y), x∈ S, y∉ T。
4.  Lx(x)+ Ly(y)+ d, x∉ S,  y∈ T。
易知,修改后对于任何边仍满足 Lx(x)+ Ly(y)>= W(x,y),并且第三种情况顶标值减少了 d,如此定会使等价子图扩大。
 
就上例而言: 修改后 Lx(1)= 2, Lx(2)= 3, Lx(3)= 5, Ly(1)= 0, Ly(1)= 0, Ly(2)= 0, Ly(3)= 1。
这时 Lx(2)+Ly(1)=3+0=3= W(2,1),在等价子图中增加了一条边,等价子图变为:
 km算法求二分图最佳匹配
如此按以上方法,得到等价子图的完美匹配。
 
另外计算 d 值的时候可以进行一些优化。
定义 slack(y)= min{ (x,y)| Lx(x)+ Ly(y)- W(x,y),x∈ S,  y∉ T }
这样能在寻找增广路径的时候就顺便将 slack 求出。
(以上为摘上网络)


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


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


hdoj 2255


#include <stdio.h>
#include <string.h>
#define M 310
#define inf 0x3f3f3f3f


int n,nx,ny;
int link[M],lx[M],ly[M],slack[M];    //lx,ly为顶标,nx,ny分别为x点集y点集的个数
int visx[M],visy[M],w[M][M];


int DFS(int x)
{
    visx[x] = 1;
    for (int y = 1;y <= ny;y ++)
    {
        if (visy[y])
            continue;
        int t = lx[x] + ly[y] - w[x][y];
        if (t == 0)       //
        {
            visy[y] = 1;
            if (link[y] == -1||DFS(link[y]))
            {
                link[y] = x;
                return 1;
            }
        }
        else if (slack[y] > t)  //不在相等子图中slack 取最小的
            slack[y] = t;
    }
    return 0;
}
int KM()
{
    int i,j;
    memset (link,-1,sizeof(link));
    memset (ly,0,sizeof(ly));
    for (i = 1;i <= nx;i ++)            //lx初始化为与它关联边中最大的
        for (j = 1,lx[i] = -inf;j <= ny;j ++)
            if (w[i][j] > lx[i])
                lx[i] = w[i][j];


    for (int x = 1;x <= nx;x ++)
    {
        for (i = 1;i <= ny;i ++)
            slack[i] = inf;
        while (1)
        {
            memset (visx,0,sizeof(visx));
            memset (visy,0,sizeof(visy));
            if (DFS(x))     //若成功(找到了增广轨),则该点增广完成,进入下一个点的增广
                break;  //若失败(没有找到增广轨),则需要改变一些点的标号,使得图中可行边的数量增加。
                        //方法为:将所有在增广轨中(就是在增广过程中遍历到)的X方点的标号全部减去一个常数d,
                        //所有在增广轨中的Y方点的标号全部加上一个常数d
            int d = inf;
            for (i = 1;i <= ny;i ++)
                if (!visy[i]&&d > slack[i])
                    d = slack[i];
            for (i = 1;i <= nx;i ++)
                if (visx[i])
                    lx[i] -= d;
            for (i = 1;i <= ny;i ++)  //修改顶标后,要把所有不在交错树中的Y顶点的slack值都减去d
                if (visy[i])
                    ly[i] += d;
                else
                    slack[i] -= d;
        }
    }
    int res = 0;
    for (i = 1;i <= ny;i ++)
        if (link[i] > -1)
            res += w[link[i]][i];
    return res;
}
int main ()
{
    int i,j;
    while (scanf ("%d",&n)!=EOF)
    {
        nx = ny = n;
      //  memset (w,0,sizeof(w));
        for (i = 1;i <= n;i ++)
            for (j = 1;j <= n;j ++)
                scanf ("%d",&w[i][j]);
        int ans = KM();
        printf ("%d\n",ans);
    }
    return 0;
}
0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 皮帘子有胶了怎么办 12v插口没有电怎么办 吃了一颗聚乙烯醇怎么办 立式注塑机产品粘膜怎么办 被小松鼠咬了怎么办 被宠物松鼠咬了怎么办 手被松鼠咬出血怎么办 银联认证码失败怎么办 国际汇款触发合规查询怎么办 外面的网线断了怎么办 网线被别的车挂断了怎么办 施工挖断军用光缆怎么办 不小心挖断光缆怎么办 家里的光纤断了怎么办 车被树枝刮花了怎么办 货车撞断了树枝怎么办? 把光缆挖断了怎么办 光缆有外伤断了怎么办 不小心挖断移动光缆怎么办 派克服内胆掉毛怎么办 汉王电纸书怎么无法开机怎么办? 压力喷水壶坏了怎么办 行车记录仪后摄像头不清楚怎么办 蓝牙听歌声音小怎么办 对讲机时灵时不灵怎么办 黑凉粉煮稀了怎么办 轮速传感器坏了怎么办 黑魂3被入侵了怎么办 轮胎螺丝滑牙了怎么办 gta5ol寻宝任务退出了怎么办 gta5线上模式买房子之后怎么办 开摩托车忘记带安全头盔怎么办 gta不想要车了怎么办 空气滤芯进水会怎么办 车胎扎了个钉子怎么办 德罗索没导弹了怎么办 CF手雷包不能用怎么办 cfAK爆头碰到狙怎么办 玩cf网络延迟高怎么办 逆水寒装备分解错了怎么办 轴与孔间隙过大怎么办