- 详细讲解:
http://blog.youkuaiyun.com/pi9nc/article/details/11848327 - 以下为粗略概括:
二分图定义:
简单来说,就是图中的点可以被分为两组,并且使得所有的边都跨越组的边界,则这就是一个二分图。另一个等价定义是:不含有“奇数条边的环”的图。匹配:
在图论中,匹配是边的集合,这个集合中任意两条边都没有公共定点。最大匹配:
一个图的所有匹配中,所喊匹配变数最多的匹配,称为这个图的最大匹配。完美匹配:
如果一个图的某个匹配中,所有的顶点都是匹配点,那么它就是一个完美匹配。交替路:
从一个未匹配点出发,依次经过非匹配边,匹配边,非匹配边……形成的路径叫交替路。增广路:
从一个未匹配点出发,走交替路,如果途径另一个未匹配点(出发的点不算),则这条交替路称为增广路。匈牙利算法:
增广路有一个重要特点,非匹配边比匹配边多一条。因此,增广路的意义是改进匹配。只要把增广路中的匹配边和非匹配边的身份交换即可。这样既不会破坏匹配的性质,交换后,途中的匹配边数比原来多了一条。我们可以通过不停地找增广路来增加匹配中的匹配边和匹配点。找不到增广路的时候,达到最大匹配(这是增广路定理)。下面给出匈牙利算法的DFS和BFS代码:
邻接表储存(顶点,边的编号均从0开始):
struct Edge
{
int from;
int to;
int weight;
Edge(int f,int t, int w):from(f),to(t), weight(w){}
};
vector<int>G[__maxNodes];
/* G[i] 存储顶点 i 出发的边的编号 */
vector<Edge> edges;
typedef vector<int>::iterator iterator_t;
int num_nodes;
int num_left;
int num_right;
int num_edges;
- DFS版本:
int matching[__maxNodes]; // 存储求解结果
int check[__maxNodes];
bool DFS(int u)
{
for(iterator_ti = G[u].begin(); i!=G[u].end(); i++)//对u的每个邻接点
{
int v = edges[*i].to;
if(!check[v])//要求不在交替路中
{
check[v] = true; //放入交替路
if(matching[v] == -1 || DFS(matching[v])) //如果是未覆盖点,说明交替路为增广路,则交换路径,并返回成功
{
matching[v] = u;
matching[u] = v;
return true;
}
}
}
return false //不存在增广路,返回失败
}
int Hungarian()
{
int ans = 0;
memset(matching, -1, sizeof(matching));
for(int u = 0;u<num_left;u++)
{
if(matching[u] == -1)
{
memset(check, 0, sizeof(check));
if(DFS(u))
ans++;
}
}
return ans;
}
- BFS版本:
queue<int> Q;
int pre[__maxNodes];
int Hungarian()
{
int ans = 0;
memset(matching, -1, sizeof(matching));
memset(check, -1, sizeof(check));
for(int i=0;i<num_left;i++)
{
if(matching[i] == -1)
{
while(!Q.empty())
Q.pop();
Q.push(i);
prev[] = -1;//设i为路径起点
bool flag = false;//尚未找到增广路
while(!Q.empty()&&!flag)
{
int u = Q.front();
for(iterator_tix = G[u].begin(); ix!=G[u].end()&&!flag;ix++)
{
int v = edges[*ix].eo;
if(check[v]!=i)
{
check[v] = i;
Q.push(matching[v]);
if(matching[v] >= 0)
{//此为匹配点
prev[matching[v]] = u;
}
else
{//找到未匹配点,交替路变增广路
flag = true;
int d=u,e=v;
while(d!=-1)
{
int t=mathcing[d];
matching[d] = e;
matching[e] = d;
d = prev[d];
e = t;
}
}
}
}
Q.pop();
}
if(matching[i]!=-1)
ans++;
}
}
return ans;
}
匈牙利算法的要点:
- 从左边第 1 个顶点开始,挑选未匹配点进行搜索,寻找增广路。
- 如果经过一个未匹配点,说明寻找成功。更新路径信息,匹配边数 +1,停止搜索。
- 如果一直没有找到增广路,则不再从这个点开始搜索。事实上,此时搜索后会形成一棵匈牙利树。我们可以永久性地把它从图中删去,而不影响结果。
- 由于找到增广路之后需要沿着路径更新匹配,所以我们需要一个结构来记录路径上的点。DFS 版本通过函数调用隐式地使用一个栈,而 BFS 版本使用 prev 数组。
- 从左边第 1 个顶点开始,挑选未匹配点进行搜索,寻找增广路。
性能比较:
两个版本的时间复杂度均为 O(V⋅E) 。DFS 的优点是思路清晰、代码量少,但是性能不如 BFS。我测试了两种算法的性能。对于稀疏图,BFS 版本明显快于 DFS 版本;而对于稠密图两者则不相上下。在完全随机数据 9000 个顶点 4,0000 条边时前者领先后者大约 97.6%,9000 个顶点 100,0000 条边时前者领先后者 8.6%, 而达到 500,0000 条边时 BFS 仅领先 0.85%。定理:
最小点覆盖 = 最大匹配数;
最小路径覆盖 = 总点数 - 最大匹配数;
最大独立集数 = 总点数 - 最大匹配数;