二分图;php
大意: web
二分图指的是这样一种图,其全部顶点能够分红两个集合X和Y,其中X或Y中任意两个在同一集合中的点都不相连,全部的边关联在两个顶点中,刚好一个属于集合X,另外一个属于集合Y。给定一个二分图G,M为G边集的一个子集,若是M知足当中的任意两条边都不依附于同一个顶点,则称M是一个匹配。图中包含边数最多的匹配称为图的最大匹配。算法
二分图的最大匹配有两种求法,第一种是最大流;第二种就是我如今要讲的匈牙利算法。这个算法说白了就是最大流的算法,可是它跟据二分图匹配这个问题的特色,把最大流算法作了简化,提升了效率。网络
增广路径的定义(也称增广轨或交错轨):spa
一条增广路径(Augmenting Path)是指从 M 中没有用到的顶点开始,并从 M 中没有用到的顶点结束的交替路径。(PS:一条交替路径(Alternating Path)是指这样一条路径,其中的每一条边交替地属于或不属于匹配 M。好比说,第1、3、五条边属于 M,而第2、4、六条不属于 M,等等。)orm
因此以下图(3)中所示即为一条增广路径。游戏
结合增广路径的定义和下图所示,咱们能够理解如下结论:get
增广路径的长度一定为奇数,第一条边和最后一条边都不属于 M。it
将 M 和增广路径进行异或操做(去同存异)能够获得一个更大的匹配 M'。io
M' 比 M 的匹配数多 1。
M 为 G 的最大匹配当且仅当不存在 M 的增广路径。
最大流算法的核心问题就是找增广路径(augment path)。匈牙利算法也不例外,它的基本模式就是:
初始时最大匹配为空
while 找获得增广路径
do 把增广路径加入到最大匹配中去
可见和最大流算法是同样的。可是这里的增广路径就有它必定的特殊性。(注:匈牙利算法虽然根本上是最大流算法,可是它不须要建网络模型,因此图中再也不须要源点和汇点,仅仅是一个二分图。每条边也不须要有方向。)
算法的思路是不停的找增广路径, 并增长匹配的个数,增广路径顾名思义是指一条可使匹配数变多的路径,在匹配问题中,增广路径的表现形式是一条"交错路径",也就是说这条由图的边组成的路径, 它的第一条边是目前尚未参与匹配的,第二条边参与了匹配,第三条边没有..最后一条边没有参与匹配,而且始点和终点尚未被选择过。这样交错进行,显然他有奇数条边。那么对于这样一条路径,咱们能够将第一条边改成已匹配,第二条边改成未匹配...以此类推。也就是将全部的边进行"反色",容易发现这样修改之后,匹配仍然是合法的,可是匹配数增长了一对。另外,单独的一条链接两个未匹配点的边显然也是交错路径。能够证实。当不能再找到增广路径时,就获得了一个最大匹配,这也就是匈牙利算法的思路。
3个重要结论:
1 最小点覆盖数: 最小覆盖要求用最少的点(X集合或Y集合的都行)让每条边都至少和其中一个点关联。能够证实:最少的点(即覆盖数)=最大匹配数
2 最小路径覆盖=最小路径覆盖=|N|-最大匹配数
用尽可能少的不相交简单路径覆盖有向无环图G的全部结点。解决此类问题能够创建一个二分图模型。把全部顶点i拆成两个:X结点集中的i和Y结点集中的i',若是有边i->j,则在二分图中引入边i->j',设二分图最大匹配为m,则结果就是n-m。
3 二分图最大独立集=顶点数-二分图最大匹配
在N个点的图G中选出m个点,使这m个点两两之间没有边,求m最大值。
若是图G知足二分图条件,则能够用二分图匹配来作.最大独立集点数 = N - 最大匹配数。
例题:
http://acm.hdu.edu.cn/showproblem.php?pid=1281棋盘游戏
一种建图的方式,对于一个坐标,x , y 能够分红两个不一样的集合,若是该点知足某种性质的话,就在 x , y 上连一条线,本题就是这样的……
本题要求关键点,那么只须要对于每一个可行点进行删点,而后看看得出的最大匹配是否小于不删点的解,若是小于,则是关键点……统计一下便可。
代码:
[cpp] view plaincopy
/*==================================================*\
| 二分图匹配(匈牙利算法DFS 实现)
| INIT: g[][]邻接矩阵;
| 优势:实现简洁容易理解,适用于稠密图,DFS找增广路快。
| 找一条增广路的复杂度为O(E),最多找V条增广路,故时间复杂度为O(VE)
==================================================*/
#include<stdio.h>
#include<memory.h>
bool g[101][101]; //邻接矩阵,true表明有边相连
bool visit[101]; //记录V2中的某个点是否被搜索过
int match[101]; //记录与V2中的点匹配的点的编号
int n,m,k; //二分图中左边、右边集合中顶点的数目
// 匈牙利算法
bool dfs(int u)
{
for (int i = 1; i <= m; ++i)
{
if (g[u][i] && !visit[i]) //若是节点i与u相邻而且未被查找过
{
visit[i] = true; //标记i为已查找过
if (match[i] == -1 || dfs(match[i])) //若是i未在前一个匹配M中,或者i在匹配M中,可是从与i相邻的节点出发能够有增广路径
{
match[i] = u; //记录查找成功记录,更新匹配M(即“取反”)
return true; //返回查找成功
}
}
}
return false;
}
inline int MaxMatch()
{
int i,sum=0;
memset(match,-1,sizeof(match));
for(i = 1 ; i <= n ; ++i)
{
memset(visit,false,sizeof(visit)); //清空上次搜索时的标记
if( dfs(i) ) //从节点i尝试扩展
{
sum++;
}
}
return sum;
}
int main(void)
{
int i,j,ans,x,y,num,t=1;
while (scanf("%d %d %d",&n,&m,&k)!=EOF)
{
memset(g,false,sizeof(g)); //初始化
for (i = 1; i <= k; ++i)
{
scanf("%d %d",&x,&y);
g[x][y] = true;
}
ans = MaxMatch();
num = 0;
for (i = 1; i <= n; ++i)
{
for (j = 1; j <= m; ++j)
{
if(g[i][j] == true)
{
g[i][j] = false;
if(MaxMatch() < ans)
num++;
g[i][j] = true;
}
}
}
printf("Board %d have %d important blanks for %d chessmen.\n",t++,num,ans);
}
return 0;
}
ps:对于二分图方面的题 难的不是代码 而是你是否能看出这是一道二分图的题 是否有这种转化能力 固然 若是你知道了 套个模板基本就能对