二分图及其应用(Bipartite Graph)
什么是二分图?
如果一个图的顶点可以分成两个集合X和Y,图的所有边一定是有一个顶点属于集合X,另一个顶点属于集合Y,则称该图为”二分图”或“二部图”。
典型问题,婚配问题。
二分图如图所示:
二分图的最大匹配
在二分图的应用中,最常见的就是求——最大匹配,很多其他的问题都可以转化为匹配问题来解决。
那么我们如何求二分图的最大匹配呢?
下面介绍一个算法:匈牙利算法
匈牙利算法示意图 1:
匈牙利算法示意图 2:
匈牙利算法示意图 3:
匈牙利算法示意图 4:
最小顶点覆盖
在二分图中求最少的点,让每条边都至少和其中的一个点关联,这就是:二分图的“最小顶点覆盖”。
一句话记忆:最小顶点覆盖就是用最少的点覆盖所有的边。
总结:
二分图的最小顶点覆盖数 == 二分图的最大匹配数
关键点:求二分图的最大匹配数。
DAG(有向无环图)的最小路径覆盖
用尽量少的不相交简单路径覆盖有向无环图(DAG)的所有顶点,这就是——DAG图的最小路径覆盖问题。
一句话记忆:
最小路径覆盖就是用最少的路覆盖所有的点。
DAG图的最小路径覆盖数=节点数(n)-最大匹配数(m)
关键:求二分图的最大匹配数
二分图的最大独立集
二分图的最大独立集数=节点数(n)-最大匹配数(m)
关键:求二分图的最大匹配数
下面我们来看一道题目:任务安排
解题代码
// 二分匹配匈牙利算法的DFS实现
// 邻接矩阵形式
// uN是匹配左边的顶点数
// vN是匹配右边的定点数
// 优点:适用于稠密图
// 优点:实现简洁易于理解
// 时间复杂度:O(VE)
// 顶点编号从0开始的
// 调用:res=hungary();
// 输出最大匹配数
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 510;
int uN,vN; // u,v的数目
int g[MAXN][MAXN]; // 邻接矩阵
int linker[MAXN]; // 存右点对象
bool used[MAXN]; // 右点访问否
bool dfs(int u)
{
for(int v=0;v<vN;v++)
if(g[u][v] && !used[v])
{
used[v] = true;
if(linker[v]==-1 || dfs(linker[v]))
{
linker[v]=u;
return true;
}
}
return false; // 这一句容易忘记
}
int hungary()
{
int res=0;
memset(linker,-1,sizeof(linker));
for(int u=0;u<uN;u++)
{
memset(used,false,sizeof(used));
if(dfs(u))
res++;
}
return res;
}
int main()
{
int k;
while(scanf("%d",&uN)==1 && uN)
{
cin>>vN>>k;
memset(g,0,sizeof(g));
int id,u,v;
while(k--)
{
cin>>id>>u>>v;
if(u!=0 && v!=0)
g[u][v]=1;
}
cout<<hungary()<<endl;
}
return 0;
}
运行结果
总结
什么是二分图
二分图的最大匹配
匈牙利算法
二分图的最小顶点覆盖
DAG图的最小路径覆盖
二分图的最大独立集