二分图最大权匹配 (KM算法)

二分图最大权完美匹配KM算法是在一个二分图里,求一个最大权匹配,但是要求这个匹配必须是完美匹配。如果匹配不一定是完美匹配,那么似乎只能将其转化为最小费用最大流来做了。我们可以使用KM算法对任意带权(无论正负权)二分图求最大/最小权完美匹配,它的算法复杂度是O(n3),但是如果写得不好会变成O(n4)。

KM算法是通过给每个顶点一个标号(我们有时称之为顶标)来把求最大权匹配的问题转化为求完备匹配的问题的。我们令二分图中X部的节点的顶标为Ai,Y部的节点的顶标为Bi。X部与Y部节点之间的权值为Wi,j,那么,在算法进行的过程中,我们必须始终保持$A_i+B_i\geq W_{i,j}$成立。因为KM算法的正确性基于以下定理:

若由二分图中所有满足Ai+Bi=Wi,j的边 (i,j)构成的子图(称做相等子图)有完备匹配,那么这个完备匹配就是二分图的最大权匹配。

初始时为了使$A_i+B_i\geq W_{i,j}$成立,令Ai为所有与Xi顶点关联的边的权值的最大值,令Bi=0。如果当前的相等子图没有完备匹配,就需要修改顶标以使扩大相等子图,直到相等子图具有完备匹配为止(这样就可以求出最大权匹配)。

如果当前相等子图找不到完备匹配,那么是因为对于某个X顶点,我们找不到一条从它出发的交错路。这时我们获得了一棵交错树,现在我们把交错树中X部的顶点的顶标全都减小某个值d,Y部的顶点的顶标全都增加d,就可以使得至少有一条新边进入相等子图。这里d=min{Ai+Bi-Wi,j},而且Xi在交错树中,Yi不在。

但是如果仅仅是这样,用朴素的方法实现,算法复杂度是O(n4)。我们给每个Y部的顶点一个“松弛量”slack,每次开始找增广路时松弛量初始化为无穷大。在寻找增广路的过程中,检查边(i,j)时,如果它不在相等子图中,则让slackj变成原值与Ai+Bi-Wi,j的较小值。这样,在修改顶标时,取所有不在交错树中的Y顶点的slack值中的最小值作为d值即可。但还要注意一点:修改顶标后,要把所有的slackj值都减去d。经过这样一个优化实现后,算法复杂度就变成了O(n3)。 

模板 " 

      

#include

#include
#define N 1001
#define M 1<<30

int n;
int m[N][N]={0};
int match[N]={0};
int lx[N]={0},ly[N]={0};
bool x[N]={0},y[N]={0};

int Min(int a,int b) {return a

void init()       //初始化
{
    int i,j;
    for(i=1;i<=n;i++)
        for(j=1;j<=n;j++)
            if(m[i][j]>lx[i]) lx[i]=m[i][j];      //lx[i]赋边的值,ly[i]=0;
    memset(match,-1,sizeof(match));
}

bool Find(int a)         //用匈牙利找最大匹配
{
    int i,d;
    x[a]=true;            //加入x子图.

    for(i=1;i<=n;i++)
    {
        if(!y[i]&&m[a][i]==lx[a]+ly[i])    //y必须没被访问
        {
            y[i]=true;
            if(match[i]==-1||Find(match[i]))
            {
                match[i]=a;
                return true;
            }
        }
    }
    return false;
}

void KM()
{
    int i,j,k,d;
    int cost=0;
    init();

    for(i=1;i<=n;i++)
    {
        while(1)
        {
            memset(x,0,sizeof(x));
            memset(y,0,sizeof(y));

            if(Find(i)) break;          //若找到最大匹配,结束.每一找到,则修改顶标

            d=M;     //初始修改量,尽量大.

            for(j=1;j<=n;j++)
                if(x[j])                              //j点在x集合
                    for(k=1;k<=n;k++)
                        if(!y[k])                    //k点还没加入进来
                            d=Min(d,lx[j]+ly[k]-m[j][k]);   // 找最小的d

            if(d==M) break;    //没找到

            for(j=1;j<=n;j++)          //修改lx,ly
            {
                if(x[j]) lx[j]-=d;
                if(y[j]) ly[j]+=d;
            }
        }

    }

    for(i=1;i<=n;i++)    //最大的权值.
    {
        cost+=m[match[i]][i];
    }
    printf("\n%d\n",cost);
}

int main()
{
    int i,j;
    scanf("%d",&n);

    for(i=1;i<=n;i++)
        for(j=1;j<=n;j++)
            scanf("%d",&m[i][j]);

    KM();
    scanf("%d",&i);
}


你可能感兴趣的:(二分图匹配)