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

基本概念
图的基本概念
二分图
设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标记为占用。

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

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

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

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标记为占用。

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;
}
















