引用来自https://www.renfei.org/blog/bipartite-matching.html
二分图:把一个图G的所有顶点划分为两个不相交集 L 和 R ,使得图G中每一条边都分别连接 L , R 中的顶点。如果存在这样的划分,则此图为一个二分图。
匹配:一个「匹配」(matching)是一个边的集合,其中的任意两条边都没有公共顶点。
最大匹配:在一个图的所有匹配中,存在一个匹配,它所含的边的数量是所有匹配中最多的,该匹配称为这个图的最大匹配。
交替路:从一个未匹配点出发,依次经过非匹配边、匹配边、非匹配边……形成的一条路径叫做交替路。
增广路:对于一条交替路,其终点若为一个未匹配点(出发点不算),则这条交替路称为增广路(agumenting path)。
(因为交替路的定义使得其,除了路径的起点和终点外,不存在未匹配点)
增广路有一个重要特点:
非匹配边比匹配边多一条。因此,研究增广路的意义是改进匹配,对增广路的增广:只要把增广路中的匹配边和非匹配边的身份交换即可。
由于中间的匹配节点不存在其他相连的匹配边,所以这样做不会破坏匹配的性质。交换后,图中的匹配边数目比原来多了 1 条。
那么根据增广路定理,找不到增广路时达到最大匹配;
故因此得到匈牙利算法:
从L集的第1个顶点开始,枚举未匹配点 i 进行搜索,寻找增广路。
若走到了某个未匹配点,则代表寻找到了一条增广路,立即进行增广,匹配边数 +1;
然后显然 i 点成为一个匹配点,根据交替路的定义所以不再有从 i 点出发的交替路,也就没有增广路,故停止以 i 点为起点的搜索,进入下一个点。
如果一直没有找到增广路,则也不再从 i 点搜索,直接进入下一个点。
匈牙利算法模板:
struct Edge{ int u,v; }; vector<Edge> E; vector<int> G[MAX]; int lN,rN;//二分图L,R集各自的顶点数 int match[MAX]; int vis[MAX]; bool dfs(int u) { for(int i=0,_size=G[u].size();i<_size;i++) { int v=E[G[u][i]].to; if (!vis[v]) { vis[v]=1; if(!matching[v] || dfs(matching[v]))//若遇到未匹配点,直接进行增广;否则继续搜索. { matching[v]=u; matching[u]=v; return true; } } } return false; } int hungarian() { int ret=0; memset(matching,0,sizeof(matching)); for(int i=1;i<=lN;i++) { if(!matching[i]) { memset(vis,0,sizeof(vis)); if(dfs(i)) ret++; } } return ret; }
复杂度O(|V|*|E|).