匈牙利算法

来源:互联网 发布:怎么退出手机淘宝账号 编辑:程序博客网 时间:2024/05/22 16:23

匈牙利算法是由匈牙利数学家Edmonds于1965年提出,因而得名。匈牙利算法是基于Hall定理中充分性证明的思想,它是部图匹配最常见的算法,该算法的核心就是寻找增广路径,它是一种用增广路径求二分图最大匹配的算法。

下面我们就用具体的例子来解释吧。

假设某小学组织了一次春游,为了安全考虑,要求每个女生都找一个男生作为自己的搭档,但男生认识该女生才会答应。我们现在假设女生用大写字母表示,男生用小写字母表示。

我们假设A和a认识,A也和b认识,B和b认识,B也和c认识,c只认识A。入下图所示:


现在我们来求有多少对学生能够出去。

对于上面的例子我们可以找到两种分配方案。


很显然,后面这种方案更好。我们把一种分配方案叫做匹配,那么问题就演变成求二分图的最大匹配(匹配对数最多)。我们很容易想到的方法是找出所有的

方案,然后输出匹配对数最多的。等等,这种方法的时间复杂度是非常高的。此路不通,请回吧。

我们可以换种思路,首先从A号女生开始考虑。既然她可以与a号男生匹配,那就让她和a号男生匹配吧,同理B号女生和b号男生匹配。接下来就是C号女生了,可是她值认识a号男生,然而a号男生已经和A号女生匹配了。那C号女生就去不了了,是不是就这样放弃了呢?这样就放弃了就没意思了。C号女生硬着头皮到了到了a号男生面前,问可不可以一起组队,a号男生说,我刚刚已经答应了A号女生和她组队了,你等等,我问下她是否还可以和其他男生组队,如果可以我们就组队。这时a号男生来到A号女生面前,问她是否还可以和其他人组队。A号女生就开始重新找人组队了。A号女生来到b号男生面前问是否可以一起组队。后面和前面的一段类似。后面B号女生找到了c号男生组队,B号女生和b号男生说已经找到其他人组队了,b号男生和A号女生说B已经找到组队的人了,现在他们两个可以组队了,然后a号男生对C号女生说他们两个可以组队了,A号女生找到组队的人了。

就是下面这张图了


是不是觉得有点绕,还有点一步三折的感觉,O(∩_∩)O哈哈哈~,不过通过这样的连锁反应,匹配对数从原来的两对变成了三对,增加了一对。对了,忘了介绍,刚才那个过程有个很神奇的名字,叫做增广路,核心思想就是和自己连接的那个点已经被使用了,那么就判断一下和那个点连接的点是否可以和其他的点连接。不难发现每找到一条增广路,匹配对数都会加一。增广路的本质就是一条路径的起点和终点都是未匹配的点。

既然增广路的作用是改进匹配方案(增加匹配对数),如果我们已经找到一种匹配方案,如何确定当前这个匹配方案已经是最大匹配了呢?如果在当前匹配方案下再也找不到增广路,那么当前匹配就是最大匹配了,算法如下:

1、首先从任意一个未被配对的点u开始,从点u的边中任意挑出一条边(假设这条边是u->v)开始匹配。如果此时v点还没有被配对,则配对成功,此时便找到了一条增广路(只不过这条增广路比较简单就找到了)。如果此时v点已经被配对了,那么就要开始进行尝试前面比较绕口的“连锁反应”。如果尝试成功了,则找到一条增广路,此时需要更新原来的配对关系。这里用一个pre数组来记录配对关系,比如u和v配对成功了,就记作pre[v]=u和pre[u]=v。匹配成功后匹配数加一。配对过程可以用深度优先搜索来实现,当然广度优先搜索也可以。

2、如果刚才所选的边匹配失败,要从u的边中再选择一条边,进行尝试。直到点u配对成功或者尝试过了所有的边为止。

3、接下来继续对剩下没有配对的点进行一一配对,直到所有的点都尝试完成,找不到新的增广路为止。

4、输出配对数

下面

一个完整的代码作为参考

#include<cstdio>#include<iostream>#include<cstring>int map[110][110]; int pre[110];int n,m;bool book[110];int dfs(int u){for(int i = 1;i <= n;i++){if(!book[i]&&map[u][i]){book[i] = true;//标记已经访问过了//如果i未被配对或者找到了新的配对 if(pre[i] == 0||dfs(pre[i])){pre[i] = u;pre[u] = i;return 1;}}}return 0;}int main(){scanf("%d%d",&n,&m);//n个点m条边 int a,b;memset(map,0,sizeof(map));//初始化地图为0 memset(pre,0,sizeof(pre));//初始化为和每个点连接的点都是0 while(m--){scanf("%d%d",&a,&b);map[a][b] = 1;map[b][a] = 1; //无向图 }int cnt = 0;for(int i = 1;i <= n;i++){memset(book,false,sizeof(book));//每次搜索前都要清空标记 if(dfs(i)){cnt++;//寻找增广路,如果找到,配对数加一 }}printf("%d",cnt);return 0;}

以下是一些技巧总结。

二分图的最小顶点覆盖:在二分图中求最少的边,让每条边至少和其中的一个点关联

最小顶点覆盖=最大匹配数

DAG图(无回路有向图(Directed Acyclic Graph))的最小路径覆盖:用尽量少的不想交的简单路径覆盖图中的所有顶点

最小路径覆盖=顶点数-最大匹配数

无向图的最小路径覆盖:

无向二分图的最下路径覆盖=顶点数-最大二分匹配/2(因为无向图就是双向的一条边等于两次入图正向和反向,最后得到的匹配数多了一倍所以要除以2才是原本的匹配数)

点可以重复走的最小路径覆盖:

【题意】:派机器人去火星寻宝,给出一个无环的有向图,机器人可以降落在任何一个点上,再沿着路去其他点探索,我们的任务是计算至少派多少机器人就可以访问到所有的点。有的点可以重复去。

【思路】:我们仍可将问题转换为最小路径覆盖。如果一个人需要经过另一个人走过的点时候,让他直接从该点上空飞过去,越过该点,直接走下一个点。如果我们赋予每个人这种能力,那么求得的无重复点的最小路径覆盖结果,就是题目要求的结果,因为需要重复的地方只要飞过去,就可以不重复了。赋予这个能力的方法就是吧所有点能间接到达的点全都改为直接到达。

二分图的最大独立集:在二分图中任意两点都不相邻的顶点的最大集合

最大独立集=结点数-最大匹配数

二部图的多重匹配:一般的二部图只能匹配一个点,在多重匹配中,一个点可以匹配多给点


原创粉丝点击