今天做了牛客华为机试的一道困难题——素数伴侣,刚开始想的使用dfs去遍历求解,后来看到题解有人使用了匈牙利算法,而这个算法之前听说过很多次了,所以就正好花了一些时间把这个算法了解了。总的来说还是比较简单(害,后悔选课没选图论了,感觉能学很多关于图方面的算法)。
一.基础概念
在了解匈牙利算法前,首先要了解一些概念:
1.二分图
首先二分图是指,顶点集V可以分割成两个集合,其中每个集合内部不存在边的关系,只有分在两个集合的顶点之间才会有边的关系。如下图所示:
2.匹配
匹配是指在图中,一组没有公共顶点的边的集合。其中有两个重点:
1.匹配是边的集合
2.任意两条边之间不存在公共顶点
3.最大匹配
最大匹配数是指在图中的所有匹配中,边数最多的匹配即为最大匹配,而其边的数量就称为最大匹配数。而匈牙利算法主要就是用来求二分图的最大匹配数的。
4.最小覆盖顶点
最小顶点覆盖是指最少的顶点数使得二分图中的每条边都至少与其中一个点相关联
最大匹配数其实就是最小覆盖顶点,因此都可以通过匈牙利算法求解
二.匈牙利算法
匈牙利算法主要求解的问题是二分图的最大匹配数的问题。我们用以下例子来讲解:
现在我们有男生和女生两个点集,其中他们之间的联系为是指其对应的暧昧关系(不考虑其他性取向哈!只有男女生之间有暧昧关系,满足二分图)。而我们现在的任务就是根据他们的暧昧关系,来选择出最多匹配的情侣数。
匈牙利算法是这么运作的:
首先可以从B1开始看,B1可以选择的暧昧的有G2和G4,那么可以先选择G2作为其对象(这只是暂时的,并不是最终结果),所以就有以下的连接
此时,到为B2男生开始选择对象了,可以看到B2只有G2这个暧昧对象,而G2已经被B1选择了,而其实B1还有G4可以选择。因此匈牙利算法会将原来B1到G2这个连接关系断掉,然后让B1去找G4,B2就可以连接到G2了(其实就可以理解成,反正我有很多选择,而同个集合之间的顶点们都是兄弟姐妹,如果自己有多余的选择,那我会优先让出我兄弟姐妹需要的那个人)
接下来就是B3了,B3就直接和G1连接上了,最后是B4,因为B4只和G4有暧昧关系,但G4已经被B1选中了,并且B1的其他选择也让给了好兄弟B2,因此B4是选择不了G4的,故最后这4对人中,只有3对匹配关系,这就是最大匹配数。
三.匈牙利算法的代码
int M, N; //M, N分别表示左、右侧集合的元素数量
int Map[MAXM][MAXN]; //邻接矩阵存图
int p[MAXN]; //记录当前右侧元素所对应的左侧元素
bool vis[MAXN]; //记录右侧元素是否已被访问过
bool match(int i)
{
for (int j = 1; j <= N; ++j)
if (Map[i][j] && !vis[j]) //有边且未访问
{
vis[j] = true; //记录状态为访问过
if (p[j] == 0 || match(p[j])) //如果暂无匹配,或者原来匹配的左侧元素可以找到新的匹配
{
p[j] = i; //当前左侧元素成为当前右侧元素的新匹配
return true; //返回匹配成功
}
}
return false; //循环结束,仍未找到匹配,返回匹配失败
}
int Hungarian()
{
int cnt = 0;
for (int i = 1; i <= M; ++i)
{
memset(vis, 0, sizeof(vis)); //重置vis数组
if (match(i))
cnt++;
}
return cnt;
}