匈牙利算法

匈牙利算法是一种用于求解任务分配问题的组合优化算法,用于解决最大匹配问题。假定存在a个人和b个任务,每个人对应多个任务,每个任务也对应多个人,我们希望提供一种策略,尽可能的为每个人分配一个独立的任务,则可以使用匈牙利算法。

python 匈牙利算法和km 图论 匈牙利算法_python 匈牙利算法和km

基本概念

图的基本概念

二分图

设G = (V, E)是一个无向图,表示V个顶点,E条边。若能将G的顶点V划分为两个不想交的子集V1和V2,并且任意边的两个端点都分属于两个集合,则称图G为一个二分图。
例如图G1可以分为两个子集a = (a1, a2, a3, a4, a5, a6)和b = (b1, b2, b3, b4, b5, b6, b7)。

最大匹配

在图G = (V, E)中,取边集E的子集M,如果M中的任意两条边都不依附于同一个顶点,则称M是一个匹配;边数最大的M则称为图G的最大匹配。
例如:图G1中{(a1, b1), (a6, b4)}为G1的一个匹配;而{(a1, b1), (a2, b2), (a3, b7), (a4, b6), (a5, b4)}为G1的一个最大匹配。

完备匹配

如果在一个匹配M中,图的每个顶点都和M中的某条边相关联,则称此匹配为完全匹配,也称完备匹配。

匈牙利算法基本概念

交替路

从一个未匹配点出发,依次经过非匹配边、匹配边、非匹配边......形成的路径叫交替路。

增广路

从一个未匹配点出发,走交替路,如果途径另一个未匹配点(非出发点),则称该条路径为增广路。注意如果路径仅包含一条边,也是增广路。

匈牙利算法

匈牙利算法就是不断寻找增广路的过程。例如上面的图G1中,顶点被划分为a、b两个集合,使用匈牙利算法为a集合中每个顶点寻找一条增广路,就能得到G1的最大匹配。具体过程如下:

1.为a1寻找增广路

a1与b1、b2、b4相连,很容易就能为a1找到一条增广路{(a1, b1)},将b1标记为占用。

python 匈牙利算法和km 图论 匈牙利算法_匈牙利算法_02


2.为a2寻找增广路

a2与b2、b5相连,a2的增广路为{(a2, b2)},将b2标记为占用。

python 匈牙利算法和km 图论 匈牙利算法_最大匹配_03


3.为a3寻找增广路

a3与b1、b4、b7相连,但b1已经被占用,因此从a3出发得到增广路{(a3, b1), (b1, a1), (a1, b4)},断开边(b1, a1),连接边(a3, b1)和(a1, b4),将b4标记为占用。

python 匈牙利算法和km 图论 匈牙利算法_匈牙利算法_04


4.为a4寻找增广路

a4与b3、b4、b6相连,得到增广路{(a4, b3)},将b3标记为占用。

python 匈牙利算法和km 图论 匈牙利算法_最大匹配_05


5.为a5寻找增广路

a5与b4相连,得到增广路{(a5, b4), (b4, a1), (a1, b1), (b1, a3), (a3, b7)},断开边(b4, a1)、(b1, a3),连接边(a5, b4)、(a1, b1)、(a3, b7),将b7标记为占用。

python 匈牙利算法和km 图论 匈牙利算法_G1_06


6.为a6寻找增广路

a6仅与b4相连,不存在增广路,算法结束,得到最大匹配{(a1, b1), (a2, b2), (a3, b7), (a4, b3), (a5, b4)}。

编程实现

匈牙利算法可以使用深度优先或广度优先实现,刚才的描述过程使用的深度优先算法。下面是使用邻接表实现的深度优先遍历查找增广路的实现。

#include <cstdio>
#include <cstring>

#define MAX 2005
int na, nb, m;  //集合a的数量,集合b的数量,边的数量
int edge[MAX][MAX];
int a[MAX], b[MAX];
int f[MAX], fb[MAX];  //f用于标注b集合的节点状态,fb用于深度遍历时标注b集合中的节点是否访问过

//深度遍历查找增广路
int find(int x) {
	for (int i = 1; i <= nb; i++) {
		if (edge[x][i] && !fb[i]) {
			fb[i] = 1;
			if (!f[i] || find(b[i])) {
				a[x] = i;
				b[i] = x;
				f[i] = 1;
				return 1;
			}
		}
	}
	return 0;
}

int main() {
	scanf("%d%d%d", &na, &nb, &m);
	int x, y;
	for (int i = 0; i < m; i++) {
		scanf("%d%d", &x, &y);
		edge[x][y] = 1;
	}
	int ans = 0;
	//为a集合中的每个节点查找增广路,找到则将ans加1
	for (int i = 1; i <= na; i++) {
		memset(fb, 0, sizeof(fb));
		ans = ans + find(i);
	}
	printf("%d\n", ans);
	return 0;
}