匈牙利算法
本文参考了大牛博客:https://www.byvoid.com/blog/hungary/
设G=(V,E)是一个无向图。如顶点集V可分割为两个互不相交的子集V1,V2之
选择这样的子集中边数最大的子集称为图的最大匹配问题(maximal matching problem)
如果一个匹配中,|V1|<=|V2|且匹配数|M|=|V1|则称此匹配为完全匹配,也称作完备匹配。特别的当|V1|=|V2|称为完美匹配。
图1:
M-交错路:p是G的一条通路,如果p中的边为属于M中的边与不属于M但属于G中的边交替出现,则称p是一条M-交错路。如:路径(x1,y1,x2,y2)。
M-饱和点:对于v∈V(G),如果v与M中的某条边关联,则称v是M-饱和点,否则称v是非M-饱和点。如x1,y1,x3,y2都属于M-饱和点,而其它点都属于非M-饱和点。
匈牙利算法的思路就是寻找可增广路,每找到一条,匹配数加1.
代码:
#include <stdio.h>
#include <string.h>
#define N 10000
int cnt, head[N], used[N], mat[N], covered[N];//mat[i]的值表示与yi 配对的集合x中的某一结点,covered[i]表示结点xi 是否已经于集合y中的某一结点配对
#include <string.h>
#define N 10000
int cnt, head[N], used[N], mat[N], covered[N];//mat[i]的值表示与yi 配对的集合x中的某一结点,covered[i]表示结点xi 是否已经于集合y中的某一结点配对
//每次寻找增广路的时候,从集合x未配对的结点开始,依次遍历集合x。
int s, r, c, matches;
struct node2
{
int next, to;
}edge[N * 2];
void init()
{
memset(head, 0, sizeof(head));
memset(used, 0, sizeof(used));
memset(mat, 0, sizeof(mat));
memset(covered, 0, sizeof(covered));
cnt = matches = 0;
}
void conn(int from, int to)
{
cnt++;
edge[cnt].next = head[from];
edge[cnt].to = to;
head[from] = cnt;
}
bool crosspath(int i)
{
for (int j = head[i]; j; j = edge[j].next)
{
int end = edge[j].to;
if (!used[end])
{
used[end] = 1;
if (mat[end] == 0 || crosspath(mat[end]))
{
covered[mat[end]] = 0;
mat[end] = i;
covered[i] = 1;
return true;
}
}
}
return false;
}
void hungry()
{
int i;
for (i = 1; i <= r; i++)
{
used[i] = 1;
if (!covered[i] && crosspath(i))
matches++;
memset(used, 0, sizeof(used));
}
printf("%d\n", matches);
}
int main()
{
int u, v, i;
init();
scanf("%d%d%d", &s, &r, &c);
for (i = 0; i < s; i++)
{
scanf("%d%d", &u, &v);
conn(u, v);
}
hungry();
return 0;
}
int s, r, c, matches;
struct node2
{
int next, to;
}edge[N * 2];
void init()
{
memset(head, 0, sizeof(head));
memset(used, 0, sizeof(used));
memset(mat, 0, sizeof(mat));
memset(covered, 0, sizeof(covered));
cnt = matches = 0;
}
void conn(int from, int to)
{
cnt++;
edge[cnt].next = head[from];
edge[cnt].to = to;
head[from] = cnt;
}
bool crosspath(int i)
{
for (int j = head[i]; j; j = edge[j].next)
{
int end = edge[j].to;
if (!used[end])
{
used[end] = 1;
if (mat[end] == 0 || crosspath(mat[end]))
{
covered[mat[end]] = 0;
mat[end] = i;
covered[i] = 1;
return true;
}
}
}
return false;
}
void hungry()
{
int i;
for (i = 1; i <= r; i++)
{
used[i] = 1;
if (!covered[i] && crosspath(i))
matches++;
memset(used, 0, sizeof(used));
}
printf("%d\n", matches);
}
int main()
{
int u, v, i;
init();
scanf("%d%d%d", &s, &r, &c);
for (i = 0; i < s; i++)
{
scanf("%d%d", &u, &v);
conn(u, v);
}
hungry();
return 0;
}
下面描述是如何寻找增广路的整个过程:
开始
x1->y1是一条增广路,对增广路取反
x2->y1->x1->y2是一条增广路,对这条增广路取反
x3->y1->x2不是一条增广路,
因为x3还可以到y2,所以
x3->y2->x1->y1->x2也不是一条增广路,
继续
x3->y2->x1->y3是一条增广路,即
取反得到
over。