说到二分,我们就很容易想到二分查找算法,今年下半年……,今天所介绍的二分图和二分查找没有太大的联系,我们先来看一下它的定义:
二分图又称作二部图,是图论中的一种特殊模型。 设G=(V,E)是一个无向图,如果顶点V可分割为两个互不相交的子集(A,B),并且图中的每条边(i,j)所关联的两个顶点i和j分别属于这两个不同的顶点集(i in A,j in B),则称图G为一个二分图。(来自百度百科)
通俗一点说,就是把象棋盘开局时上的红子(集合A)和黑子(集合B)互相连线,红黑不同(A∩B=∅),而且连线双方都是不同的势力(分别属于两个集合),像这样的就叫二分图。
一般来说这种图长这样:
可以清晰地看出左右的顶点构成了两个相互独立的集合,而且这是单向图。
一般来说,对于这种图像需要面对的问题,大多是匹配问题,即两个点集A,B可以两两结合出多少种组合,二分图的最大匹配就是研究他们之间的组合的最大个数,下边介绍一种算法来解决这个问题。
匈牙利算法
百度百科上相关介绍:
匈牙利算法是一种在多项式时间内求解任务分配问题的组合优化算法,并推动了后来的原始对偶方法。美国数学家哈罗德·库恩于1955年提出该算法。此算法之所以被称作匈牙利算法,是因为算法很大一部分是基于以前匈牙利数学家Dénes Kőnig和Jenő Egerváry的工作之上创建起来的。
依托上图(设左点集为集合A,右点集为集合B,连接两个集合的线段均为从A指向B),介绍一下这种算法的思路。
- 首先根据输入确定集合点之间的对应关系(比如集合A的1点可与B中的5,7点对应,类似于映射),这需要一个二维数组relationships[amount1][amount2]来储存,(比如relationships[1][5]=1,relationships[1][6]=1)类似于邻接矩阵的建立,但是不同的是二分图是单向图,只需要建立朝向一个方向的向量即可。
- 对指向集合中的每一个点进行递归搜索(例如上图集合A中的所有点).
- 在递归搜索中,以当前点current的视角,对B集合所有点进行逐一访问(比如说图上A1对B5,B6,B7,B8进行访问),若满足relationships[current][Bi]=1,且Bi在current点的这轮访问中没有被访问过,那么执行下一步.
- 若Bi点当前没有匹配对象
(单身),或者令Bi现有的匹配对象更改匹配可行(修罗场)(比如说A2逐一访问B5时强制令A1点继续向下访问,由于条件满足,A1与B7配对。),那么Bi的匹配对象就是current点.
其中递归搜索(dfs)的代码的一个形式如下:
int relationships[500][500],partners[500];//关系数组,匹配对象数组。
bool vis[500];//标记在一轮搜索中已经被访问的点。
int dfs(int current)//需要返回值,不能为void型
{
for(int i=1;i<=Bmax;i++)//Bmax为B集合最大点数.
{
if(relationships[current][i]&&!vis[i])
{
vis[i]=1;//这个点在这一轮中被访问过了,以防下面的递归无限访问造成死循环。
if(!partners[i]||dfs(partners[i]))//如果i点没有匹配对象,或者i点可以有其他选择.
{
partners[i]=current;
return 1; //匹配成功
}
}
}
return 0;
}
//在主函数里...
for(int i=1;i<=Amax;i++)//Amax为A集合最大点数
{
memset(vis,0,sizeof(vis));//由于vis数组是用来标记一轮搜索中被访问的点的,所以一轮搜索结束后要重置。
dfs(i);//每个点都要搜索一遍
}
下面看一道例题:HDU2063
过山车Time Limit: 1000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total Submission(s): 31700 Accepted Submission(s): 13647 Problem Description RPG girls今天和大家一起去游乐场玩,终于可以坐上梦寐以求的过山车了。可是,过山车的每一排只有两个座位,而且还有条不成文的规矩,就是每个女生必须找个男生做partner和她同坐。但是,每个女孩都有各自的想法,举个例子把,Rabbit只愿意和XHD或PQK做partner,Grass只愿意和linle或LL做partner,PrincessSnow愿意和水域浪子或伪酷儿做partner。考虑到经费问题,boss刘决定只让找到partner的人去坐过山车,其他的人,嘿嘿,就站在下面看着吧。聪明的Acmer,你可以帮忙算算最多有多少对组合可以坐上过山车吗?
Input 输入数据的第一行是三个整数K , M , N,分别表示可能的组合数目,女生的人数,男生的人数。0<K<=1000
Output 对于每组数据,输出一个整数,表示可以坐上过山车的最多组合数。
Sample Input 6 3 3 1 1 1 2 1 3 2 1 2 3 3 1 0
Sample Output 3 |
由于男生和女生之间为两个独立集合,并且女生集合中的元素单方向指向了男生集合的元素。这是显然的二分图匹配。
附上AC代码:
//Battlefield control establishing....
#include <iostream>
//#include <bits/stdc++.h>
#include <cstdio>
#include <iomanip>
#include <cstring>
//#include <minmax.h>
#define MCV main
#define read(a) scanf("%lld",&a)
#define reset(a,b) memset(a,b,sizeof(a))
const int INF=0x3f3f3f3f;
typedef long double ld;
typedef long long ll;
using namespace std;
//Get ready?LET'S ROCK!
bool vis[10005];
int rel[1000][1000],fb[1000];
int k,m,n;
int dfs(int cur)
{
for(int i=1;i<=n;i++)
{
if(rel[cur][i]&&!vis[i])
{
vis[i]=true;
if(!fb[i]||dfs(fb[i]))
{
fb[i]=cur;
return 1;
}
}
}
return 0;
}
int MCV()
{
while(cin>>k&&k)
{
reset(rel,0);
reset(fb,0);
cin>>m>>n;
int tmp1,tmp2;
for(int i=1; i<=k; i++)
{
cin>>tmp1>>tmp2;
rel[tmp1][tmp2]=1;
}
int ans=0;
for(int i=1; i<=m; i++)
{
reset(vis,false);
ans+=dfs(i);
}
cout<<ans<<endl;
}
return 0;
}