二分图最大权匹配 KM算法

KM算法的正确性基于以下定理:

若由二分图中所有满足A[i]+B[i]=w[i][j]的边C(i,j)构成的子图(即相等子图)有完备匹配,那么这个完备匹配就是二分图的最大权匹配

基本概念

1.完备匹配

设G=为二分图,|V1|<=|V2|,M为G中的一个最大匹配,且|M|=V1,则称M为V1到V2的完备匹配。

通俗的理解,就是把V1中所有的点都匹配完

二分图最大权匹配 KM算法_第1张图片

 2.可行顶标

对于左边的点设为LX[maxn]数组,右边的点设为LY[maxn]数组,w[i][j]表示 v[i] 到 v[j] 的权值

3.相等子图

相等子图为完备匹配中所有的匹配,即全部V1中的点和与V1中的点匹配的V2中的点,但是边只包含 LX[i]+LY[j]=W[i][j]的边

4.最优完备匹配

最优完备匹配就是在完备匹配的条件下求解权值最大或者最小,若由二分图中所有满足A[i]+B[i]=W[i][j]的边C(i,j)构成的相等子图有完备匹配,那么这个完备匹配就是二分图的最大权匹配

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

5.交错树

对V1中的一个顶点进行匹配的时候,所标记过的V1,V2中的点以及连线,形成一个树状的图

KM算法原理

1.基本原理

该算法是通过给每个顶点一个标号(叫做顶标)来把求最大权匹配的问题转化为求完备匹配的问题。
设顶点V1的顶标lx[i],V2顶点的顶标为LY[j],顶点V1的i与V2的j之间的边权为V(i,j)。在算法执行的过程中,对于任一条边C(i,j),LX[i]+LY[i]>=V[i,j]始终成立

2.基本流程

(1)初始化时为了使 LX[i] + LY[j] >= V [i,j]恒成立,将V1的点的标号记为与其相连的最大边权值,V2的点标号记为0
(2)用匈牙利算法在相等子图寻找完备匹配
(3)若未找到完备匹配,则修改可行顶标的值,扩充相等子图
(4)重复(2)(3)直到找到相等子图的完备匹配为止

3.这里值得注意的是找完备匹配不难理解,主要是进行可行顶标的修改扩充相等子图

引用:

朴素的实现方法:时间复杂度为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值即可。但还要注意一点:修改 顶标后,要把所有的slack值都减去d。

引用:

如果当前的相等子图没有完备匹配,就按下面的方法修改顶标以使扩大相等子图,直到相等子图具有完备匹配为止。

我们求当前相等子图的完备匹配失败了,是因为对于某个V1顶点,我们找不到一条从它出发的交错路。这时我们获得了一棵交错树,它的叶子结点全部是V1顶点。现在我们把交错树中V1顶点的顶标全都减小某个值d,V2顶点的顶标全都增加同一个值d,那么我们会发现:

1)两端都在交错树中的边(i,j),lx[ i ]+ly[j]的值没有变化。也就是说,它原来属于相等子图,现在仍属于相等子图。

2)两端都不在交错树中的边(i,j),lx[ i ]和ly[j]都没有变化。也就是说,它原来属于(或不属于)相等子图,现在仍属于(或不属于)相等子图。

3)V1端不在交错树中,V2端在交错树中的边(i,j),它的lx[ i ]+ly[j]的值有所增大。它原来不属于相等子图,现在仍不属于相等子图。

4)V1端在交错树中,V2端不在交错树中的边(i,j),它的lx[ i ]+ly[j]的值有所减小。也就说,它原来不属于相等子图,现在可能进入了相等子图,因而使相等子图得到了扩大。

//HDU2255
#include
#include
#include
using namespace std;
#define maxn 310
#define INF 0x3f3f3f3f
#define clr(x)  memset(x,0,sizeof(x))
int w[maxn][maxn];//w[i][j]表示i到j的权值
int lx[maxn],ly[maxn];//同时调节两个数组,使得权值和最大
int n;
//n1,n2为二分图的顶点集,其中x属于n1,y属于n2
//link记录n2中的点y在n1中所匹配的x点的编号
int link[maxn];
int slack[maxn];//松弛操作
int visx[maxn],visy[maxn];
bool dfs(int x)
{
    visx[x]=1;//得到发生矛盾的居民集合
    //对于这个居民,每个房子都试一下,找到就退出
    for(int y=1;y<=n;y++){
        if(visy[y]) continue;//不需要重复访问
        int t=lx[x]+ly[y]-w[x][y];//这个条件下使用匈牙利算法
        if(t==0)//标志这个房子可以给这个居民
        {
            visy[y]=1;
//这个房子没人住或者可以让住着个房子的人去找另外的房子住
            if(link[y]==0||dfs(link[y]))
            {
                link[y]=x;
                return 1;//可以让这位居民住进来
            }
        }
        else if(slack[y]>t)//否则这个房子不能给这位居民
            slack[y]=t;
    }
    return 0;
}
int KM()
{
    clr(lx);
    clr(ly);
    clr(link);
    //首先把每个居民出的钱最多的那个房子给他
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
        if(lx[i]slack[k])
                d=slack[k];//找到最小松弛量
            for(int k=1;k<=n;k++)//松弛操作,使发生矛盾的居民有更多选择
            {
                if(visx[k]) lx[k]-=d;
                //将矛盾居民的要求降低,使发生矛盾的居民有更多
 
                if(visy[k]) ly[k]+=d;
                //使发生矛盾的房子在下一个子图,保持矛盾
            }
        }
    }
    int ans=0;
    for(int i=1;i<=n;i++)
        ans+=w[link[i]][i];
    return ans;
}
int main()
{
    while(~scanf("%d",&n)){
        clr(w);//每个案例都重置为0
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
            scanf("%d",&w[i][j]);//输入每条边的权值
        printf("%d\n",KM());
    }
    return 0;
}

 

你可能感兴趣的:(二分图最大权匹配 KM算法)