KM算法

    KM算法用来求解二分图的最大(小)权匹配,即最优(佳)匹配。

   该算法是通过给每一个顶点标号将求最大匹配问题转化为求完备匹配的问题。

    设顶点Xi的顶标为A[ i ],顶点Yj的顶标为B[ j ],顶点Xi与Yj之间的边权为w[i,j]。在算法执行过程中的任一时刻,对于任一条边(i,j),A[ i ]+B[j]>=w[i,j]始终成立。

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

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

    首先解释下什么是完备匹配,所谓的完备匹配就是在二部图中,X点集中的所有点都有对应的匹配或者是

  Y点集中所有的点都有对应的匹配,则称该匹配为完备匹配。

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

       二分图最优匹配模板(KM算法)

/*其实在求最大 最小的时候只要用一个模板就行了,把边的权值去相反数即可得到另外一个.求结果的时候再去相反数即可*/
/*最大最小有一些地方不同。。*/
#include <iostream>
//赤裸裸的模板啊。。
const int maxn = 101;
const int INF = (1<<31)-1;
int w[maxn][maxn];
int lx[maxn],ly[maxn]; //顶标
int linky[maxn];
int visx[maxn],visy[maxn];
int slack[maxn];
int nx,ny;
bool find(int x)
{
	visx[x] = true;
	for(int y = 0; y < ny; y++)
	{
		if(visy[y])
			continue;
		int t = lx[x] + ly[y] - w[x][y];
		if(t==0)
		{
			visy[y] = true;
			if(linky[y]==-1 || find(linky[y]))
			{
				linky[y] = x;
				return true;		//找到增广轨
			}
		}
		else if(slack[y] > t)
			slack[y] = t;
	}
	return false;					//没有找到增广轨(说明顶点x没有对应的匹配,与完备匹配(相等子图的完备匹配)不符)
}

int KM()				//返回最优匹配的值
{
	int i,j;
	memset(linky,-1,sizeof(linky));
	memset(ly,0,sizeof(ly));
	for(i = 0; i < nx; i++)
		for(j = 0,lx[i] = -INF; j < ny; j++)
			if(w[i][j] > lx[i])
				lx[i] = w[i][j];
	for(int x = 0; x < nx; x++)
	{
		for(i = 0; i < ny; i++)
			slack[i] = INF;
		while(true)
		{
			memset(visx,0,sizeof(visx));
			memset(visy,0,sizeof(visy));
			if(find(x))						//找到增广轨,退出
				break;
			int d = INF;
			for(i = 0; i < ny; i++)			//没找到,对l做调整(这会增加相等子图的边),重新找
			{
				if(!visy[i] && d > slack[i])
					d = slack[i];
			}
			for(i = 0; i < nx; i++)
			{
				if(visx[i])
					lx[i] -= d;
			}
			for(i = 0; i < ny; i++)
			{
				if(visy[i])
					ly[i] += d;
				else
					slack[i] -= d;
			}
		}
	}
	int result = 0;
	for(i = 0; i < ny; i++)
		result += w[linky[i]][i];
	return result;
}

int main()
{
	char c;
	int man[maxn][2],home[maxn][2];
	int i,j;
	int n,m;
	while(true)
	{
		scanf("%d %d",&n,&m);
		if(n==0&&m==0) break;
		nx = ny = 0;
		getchar();
		for(i = 0; i < n; i++)
		{
			for(j = 0; j < m; j++)
			{
				c = getchar();
				if(c == 'm')
				{
					man[nx][0] = i;
					man[nx][1] = j;
					nx++;
				}
				else if(c == 'H')
				{
					home[ny][0] = i;
					home[ny][1] = j;
					ny++;
				}
			}
			getchar();
		}
		for(i = 0; i < nx; i++)
			for(j = 0; j < ny; j++)
				w[i][j] = (abs(man[i][0]-home[j][0]) + abs(man[i][1]-home[j][1]))*(-1);
		printf("%d\n",KM()*(-1));
	}
	return 0;
}

 其实,二分图的最优匹配还可以用网络流(最大流)来解决,但KM算法更为简洁。

见POJ2195。

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值