带权值的二分图匹配 KM算法

1,如果二分图不是完全二分图,我们通过添加无用路径(最大匹配中,路径权值为0)和顶点使之成为完全二分图;

2,使用KM算法求解,KM算法核心需要理解feasible vertex labeling和equality subgraph概念,在equality subgraph中寻找最大匹配(采用匈牙利算法),如果最大匹配正好为完全匹配,根据KM理论,这个完全匹配就是带权值的最大匹配;如果在当前的equality subgraph获取的最大匹配不是完全匹配,我们通过KM算法中提供的修改label方法,增加新的y点,得到新的equality subgraph,再继续在新的subgraph寻找最大匹配,如此循环。

代码如下:

#include <cstdio>
#include <memory.h>
#include <algorithm>    // 使用其中的 min 函数
using namespace std;

const int MAX = 1024;

int n; // X 的大小
int weight[MAX][MAX]; // X 到 Y 的映射(权重)
int lx[MAX], ly[MAX]; // 标号
bool sx[MAX], sy[MAX]; // 是否被搜索过
int match[MAX]; // Y(i) 与 X(match [i]) 匹配

// 初始化权重
void init(int size);
// 从 X(u) 寻找增广道路,找到则返回 true
bool path(int u);
// 参数 maxsum 为 true ,返回最大权匹配,否则最小权匹配
int bestmatch(bool maxsum = true);

void init(int size)
{
	// 根据实际情况,添加代码以初始化
	n = size;
	for (int i = 0; i < n; i++)
		for (int j = 0; j < n; j++)
			scanf("%d", &weight[i][j]);
}
/*
 * 和二分图的思路类似,在子图中寻找增广路径
 */
bool path(int u)
{
	sx[u] = true;
	for (int v = 0; v < n; v++)
		if (!sy[v] && lx[u] + ly[v] == weight[u][v])
		{
			sy[v] = true;
			if (match[v] == -1 || path(match[v]))
			{
				match[v] = u;
				return true;
			}
		}
	return false;
}

int bestmatch(bool maxsum)
{
	int i, j;
	if (!maxsum)
	{
		for (i = 0; i < n; i++)
			for (j = 0; j < n; j++)
				weight[i][j] = -weight[i][j];
	}

	// 初始化标号
	for (i = 0; i < n; i++)
	{
		lx[i] = -0x1FFFFFFF;
		ly[i] = 0;
		for (j = 0; j < n; j++)
			if (lx[i] < weight[i][j])
				lx[i] = weight[i][j];
	}

	memset(match, -1, sizeof(match));
	for (int u = 0; u < n; u++)
		while (1)
		{
			memset(sx, 0, sizeof(sx));
			memset(sy, 0, sizeof(sy));
			if (path(u))	//一直寻找增广路径,直到子图中没有增广路径,我们通过修改label来增加新的点,增加的点必为y
				break;

			// 修改标号
			int dx = 0x7FFFFFFF;
			for (i = 0; i < n; i++)
				if (sx[i])
					for (j = 0; j < n; j++)
						if (!sy[j])
							dx = min(lx[i] + ly[j] - weight[i][j], dx);	//找到松弛变量最小的点
			for (i = 0; i < n; i++)
			{
				if (sx[i])
					lx[i] -= dx;
				if (sy[i])
					ly[i] += dx;
			}
		}

	int sum = 0;
	for (i = 0; i < n; i++)
		sum += weight[match[i]][i];

	if (!maxsum)
	{
		sum = -sum;
		for (i = 0; i < n; i++)
			for (j = 0; j < n; j++)
				weight[i][j] = -weight[i][j]; // 如果需要保持 weight [ ] [ ] 原来的值,这里需要将其还原
	}
	return sum;
}

int main()
{
	int n;
	scanf("%d", &n);
	init(n);
	int cost = bestmatch(true);

	printf("%d ", cost);
	for (int i = 0; i < n; i++)
	{
		printf("Y %d -> X %d ", i, match[i]);
	}

	return 0;
}

附带poj2195解法:

/*
 * poj2195.cpp
 *
 *  Created on: 2012-5-9
 *      Author: ict
 */

#include <cstdio>
#include <cstdlib>
#include <string.h>
#include <algorithm>
#include <cmath>
using namespace std;
#define MAX 200
typedef struct GRID
{
	int x;
	int y;
}grid, pgrid;
grid M[MAX], H[MAX];

int n; // X 的大小
int weight[MAX][MAX]; // X 到 Y 的映射(权重)
int lx[MAX], ly[MAX]; // 标号
bool sx[MAX], sy[MAX]; // 是否被搜索过
int match[MAX]; // Y(i) 与 X(match [i]) 匹配

/*
 * 和二分图的思路类似,在子图中寻找增广路径
 */
bool path(int u)
{
	sx[u] = true;
	for (int v = 0; v < n; v++)
		if (!sy[v] && lx[u] + ly[v] == weight[u][v])
		{
			sy[v] = true;
			if (match[v] == -1 || path(match[v]))
			{
				match[v] = u;
				return true;
			}
		}
	return false;
}

int bestmatch(bool maxsum)
{
	int i, j;
	if (!maxsum)
	{
		for (i = 0; i < n; i++)
			for (j = 0; j < n; j++)
				weight[i][j] = -weight[i][j];
	}

	// 初始化标号
	for (i = 0; i < n; i++)
	{
		lx[i] = -0x1FFFFFFF;
		ly[i] = 0;
		for (j = 0; j < n; j++)
			if (lx[i] < weight[i][j])
				lx[i] = weight[i][j];
	}

	memset(match, -1, sizeof(match));
	for (int u = 0; u < n; u++)
		while (1)
		{
			memset(sx, 0, sizeof(sx));
			memset(sy, 0, sizeof(sy));
			if (path(u))	//一直寻找增广路径,直到子图中没有增广路径,我们通过修改label来增加新的点,增加的点必为y
				break;

			// 修改标号
			int dx = 0x7FFFFFFF;
			for (i = 0; i < n; i++)
				if (sx[i])
					for (j = 0; j < n; j++)
						if (!sy[j])
							dx = min(lx[i] + ly[j] - weight[i][j], dx);	//找到松弛变量最小的点
			for (i = 0; i < n; i++)
			{
				if (sx[i])
					lx[i] -= dx;
				if (sy[i])
					ly[i] += dx;
			}
		}

	int sum = 0;
	for (i = 0; i < n; i++)
		sum += weight[match[i]][i];

	if (!maxsum)
	{
		sum = -sum;
		for (i = 0; i < n; i++)
			for (j = 0; j < n; j++)
				weight[i][j] = -weight[i][j]; // 如果需要保持 weight [ ] [ ] 原来的值,这里需要将其还原
	}
	return sum;
}



int main()
{
	int row, col;
	int i, j;
	int ch;
	int mCount, hCount;

	while(1)
	{
		scanf("%d %d", &row, &col);
		getchar();
		if(row == 0 && col == 0)
			break;
		mCount = 0;
		hCount = 0;
		for(i = 0; i < row ; i++)
		{
			for(j = 0; j < col; j++)
			{
				ch = getchar();
				if(ch == 'm')
				{
					M[mCount].x = i;
					M[mCount].y = j;
					mCount++;
				}
				else
					if(ch == 'H')
					{
						H[hCount].x = i;
						H[hCount].y = j;
						hCount++;
					}
			}
			getchar();
		}

		n = mCount;

		for(i = 0 ; i < n; i++)
			for(j = 0; j < n; j++)
			{
				weight[i][j] = abs(M[i].x - H[j].x) + abs(M[i].y - H[j].y);
			}

		printf("%d\n", bestmatch(false));
	}

	return 0;

}




  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值