第一次写博客,有什么错误请指出我会及时改正。qwq
目录
-
二分图
二分图其实就是在一个图中所有的点可以分为两组,同一组中没有边,所有的边都跨越了两个组。准确的说:把一个图的顶点划分为两个不想交的集合 U 和 V ,且使得每一条边都分别lial连接 U 、V 中的顶点,如果存在这样的划分,则称此图为二分图。
此外二分图还有一个等价定义是:不含有「含奇数条边的环」的图。
-
匹配
二分图匹配就是边的集其中任意两条边没有公共顶点。如图若1—2相连,5—4相连即为此图匹配。1—2相连,5—4相连,7—6相连也为此图匹配。
我们定义有:
匹配边、匹配点、非匹配边、非匹配点。
若1—2相连,5—4相连,7—6相连。则显然1—2边、5—4边、7—6边为匹配边,1、2、4、5、6、7为匹配点。剩下的为非匹配点和非匹配边。
-
最大匹配
一个图的匹配中所含边数最多的匹配即为此图最大匹配。像上图最大匹配即为:
显然最大匹配可能不只有一种。
-
完美匹配
如果一个图的某个匹配中,所有的顶点都是匹配点,那么它就是一个完美匹配。上图都是完美匹配。显然,完美匹配一定是最大匹配(完美匹配的任何一个点都已经匹配,添加一条新的匹配边一定会与已有的匹配边冲突)。但并非每个图都存在完美匹配。
下面进入正题匈牙利算法。
想学习匈牙利算法先普及几个概念:
-
交替路:
从一个未匹配点出发,依次经过非匹配边、匹配边、非匹配边……形成的路径叫交替路。如图2—>1—>8—>7即为一条交替路。(该图引用错误,,,大家可以把其余红边去掉,只保留1—>8这条匹配边)
-
增广路:
从一个未匹配点出发,走交替路,如果途径另一个未匹配点(出发的点不算),则这条交替路称为增广路(agumenting path)。如图1—>2—>3—>6—>7—>8即为一条增广路。
增广路有一个重要特点:非匹配边比匹配边多一条。因此,研究增广路的意义是改进匹配。只要把增广路中的匹配边和非匹配边的身份交换即可。由于中间的匹配节点不存在其他相连的匹配边,所以这样做不会破坏匹配的性质。交换后,图中的匹配边数目比原来多了 1 条。
我们可以通过不停地找增广路来增加匹配中的匹配边和匹配点。找不到增广路时,达到最大匹配(这是增广路定理)。匈牙利算法正是这么做的。
下面给出匈牙利算法的dfs代码:
代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cctype>
using namespace std;
int ans,n,m,e,u,v,link[1005],use[1005],map[1005][1005];/*map数组为邻接矩阵,use表示当前点是否匹配,link[i]表示与顶点i所连的点*/
inline int read( )
{
int x = 0;char c = getchar( );
while (!isdigit(c))
{
c = getchar( );
}
while(isdigit(c))
{
x = (x<<3) + (x<<1) + (c^48);
c = getchar( );
}
return x;
}
inline bool dfs(int x)
{
for(int i = 1; i <= m; i++)
{
if(!use[i]&& map[x][i])//若不在交替路中
{
use[i] = 1;//则加入交替路
if(!link[i] || dfs(link[i]))
{
link[i] = x;
return true;
}
}
}
return false;
}
void xyl( )
{
memset(link, 0, sizeof(link));
for(int i = 1; i <= n; i++)
{
memset(use, 0, sizeof(use));
if(dfs(i)) ans++;
}
}
int main( )
{
n = read( );
m = read( );
e = read( );
for(int i = 1; i <= e; i++)
{
u = read( );
v = read( );
map[u][v] = true;
// map[v][u]=true;
}
xyl( );
printf("%d\n",ans);
return 0;
}
匈牙利算法的要点如下
-
从左边第 1 个顶点开始,挑选未匹配点进行搜索,寻找增广路。
- 如果经过一个未匹配点,说明寻找成功。更新路径信息,匹配边数 +1,停止搜索。
- 如果一直没有找到增广路,则不再从这个点开始搜索。事实上,此时搜索后会形成一棵匈牙利树。我们可以永久性地把它从图中删去,而不影响结果。