一.定义
二分图又称作偶图。就是顶点集 V 可分割为两个互不相交的子集,且图中每条边依附的两个顶点都分属于这两个互不相交的子集,两个子集内的顶点不相邻。
当图中的顶点分为两个集合,使得第一个集合中的所有顶点都与第二个集合中的所有顶点相连时,此时是一特殊的二分图,称为完全二分图。
二.染色法判断二分图
通过二分图的定义我们可以知道,同一个点集中任意两个点之间没有边,也就是说一条边的两个顶点一定不在同一个集合。
然后我们可以根据这个性质给每个点设置一个标记,如果一个点标记为1,那么将它的直接后继结点标记为2,如果一个点被标记两次不同的值,那么说明图中存在奇数边的环,该图不是二分图。
更形象的来说,用染色的方式给每个点依次进行染色,如果一个点被染上两种颜色,则该图不是二分图。
DFS
//给定一个无向图,判断是否是二分图
#include <iostream>
#include <cstring>
using namespace std;
const long long N=1e3+10;
int edge[N][N]={0};//邻接矩阵存储
int color[N]={0};//1表示红色 -1表示黑色 0表示未染色
int n,m;//n个顶点m条边
bool dfs(int v,int c){
color[v]=c;//对v点进行染色
for(int i=1;i<=n;i++){//遍历与v点相连的点
if(edge[v][i]==1){
if(color[i]==c){//i点颜色与v相同,不是二分图
return false;
}
else if(color[i]==0){//i点没有染色
dfs(i, -c);//i点染相反的颜色
return false;
}
else{
continue;
}
}
}
return true;
}
int main(int argc, const char * argv[]) {
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
cin>>n>>m;
int u,v;
for(int i=0;i<m;i++){
cin>>u>>v;
edge[u][v]=1;
edge[v][u]=1;
}
cout<<dfs(1,1);
return 0;
}
三.匈牙利算法
匈牙利算法:
图论中寻找最大匹配的算法(暂不考虑加权的最大匹配),主要用于解决一些与二分图匹配有关的问题。
1.匹配:
1)匹配是边的集合
2)在该集合中,任意两条边不能有共同的顶点。
2.最大匹配:
选择这样的边数最大的子集称为图的最大匹配问题。最大匹配的边数称为最大匹配。
3.交替路:
从未匹配点出发,依次经过未匹配的边和已匹配的边,即为交替路。
4.最小覆盖:
二分图的最小覆盖分为最小顶点覆盖和最小路径覆盖:
1)最小顶点覆盖是指,最少的顶点数使得二分图G中的每条边都至少与其中一个点相关联,二分图的最小顶点覆盖数=二分图的最大匹配数;
2)最小路径覆盖也称为最小边覆盖,是指用尽量少的不相交简单路径覆盖二分图中的所有顶点。二分图的最小路径覆盖数=|V|-二分图的最大匹配数;
5.增广路:
如果交替路经过除出发点外的另一个未匹配点,则这条交替路称为增广路。
由增广路的定义推出下面三个结论(设P为一条增广路):
1) P的路径长度一定为奇数,第一条边和最后一条边都是未匹配的边。
2) 对增广路径编号,所有奇数的边都不在M中,偶数边在M中。
3) P经过取反操作可以得到一个更大的匹配图,比原来匹配多一个(取反操作即,未匹配的边变成匹配的边,匹配的边变成未匹配的边)
4) 当且仅当不存在关于图M的增广路径,则图M为最大匹配。所以匈牙利算法的思路就是:不停找增广路,并增加匹配的个数。
//寻找二分图的最大匹配
#include <iostream>
#include <cstring>
using namespace std;
const long long N=1e3+10;
int n,m;//n,m分别表示左侧、右侧集合的元素数量
int edge[N][N];//邻接矩阵存储图
int p[N];//记录右侧元素所对应的左侧元素
bool vis[N];//记录右侧元素是否已经被访问过
bool match(int i){
for(int j=1;j<=m;++j){
if(edge[i][j]&&!vis[j]){//有边且未访问过
vis[j]=true; //设置为访问过
if(p[j]==0||match(p[j])){//暂时无匹配,或者原来匹配的左侧元素可以找到新的匹配
p[j]=i;
return true;
}
}
}
return false;
}
int Hungarian(){
int cnt=0;
for(int i=1;i<=n;i++){
memset(vis,false,sizeof(vis));//重置vis数组
if(match(i)){
cnt++;
}
}
return cnt;
}
//寻找二分图的最大匹配
#include <iostream>
#include <cstring>
using namespace std;
const long long N=1e3+10;
typedef struct graph{
int m,n;//图左侧有m个顶点,右侧有n个顶点
int edge[N][N];//邻接矩阵
bool on_path[N];//该点是否在增广路径上
int path[N];//记录路径
int max_match;
}GRAPH_MATCH;
void output(GRAPH_MATCH *match){
for(int i=0;i<match->n;i++){
cout<<match->path[i]<<"---"<<i<<'\n';
}
}
bool find_aug_path(GRAPH_MATCH *match,int i){
for(int j=0;j<match->n;j++){
if(match->edge[i][j]&&match->on_path[j]==false){//如果i与j之间存在路径,并且j没有在已经存在的增广路径上
match->on_path[j]=true;
if(match->path[j]==-1||find_aug_path(match, match->path[j])){
//如果j是未覆盖的点或者说和j相连的点能够找到新的点与之相连
match->path[j]=i;
return true;
}
}
}
return false;
}
void Hungary_match(GRAPH_MATCH *match){
for(int i=0;i<match->m;i++){
find_aug_path(match, i);
memset(match->on_path,false,sizeof(match->on_path));
}
}
int main(int argc, const char * argv[]) {
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
GRAPH_MATCH *match=new GRAPH_MATCH;
cin>>match->m>>match->n;
memset(match->on_path,false,sizeof(match->on_path));
memset(match->path,-1,sizeof(match->path));
int edge,u,v;
cin>>edge;
for(int i=0;i<edge;i++){
cin>>u>>v;
match->edge[u][v]=1;
}
return 0;
}
四.实战演练
谈恋爱
1.题目描述:
有a个男生,编号1,...,a,和b个女生,编号1,...,b。
第个男生想和第个女生谈恋爱。
在女生都愿意的情况下,最多出现几对情侣。
2.输入描述:
第一行为a,b,m。
接下来的m行每行包含两个正整数u,v,保证
3.输出描述:
一个整数,表示最多产生多少对情侣。
4.代码实现:
//谈恋爱
#include <iostream>
#include <cstring>
using namespace std;
const long long N=5e2+10;
int a,b,m;
int edge[N][N]={0};
int p[N]={0};//女生所对的男生
bool vis[N]={false};//女生是否被访问过
bool match(int i){
for(int j=1;j<=b;j++){
if(edge[i][j]&&!vis[j]){//有边并且没有被访问过
vis[j]=true;
if(p[j]==0||match(p[j])){//没有匹配或者说原来匹配的左侧元素可以找到新的元素进行匹配
p[j]=i;
return true;
}
}
}
return false;
}
int Hungarin(){
int cnt=0;
for(int i=1;i<=a;i++){
memset(vis,false,sizeof(vis));
if(match(i)){
cnt++;
}
}
return cnt;
}
int main(int argc, const char * argv[]) {
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
cin>>a>>b>>m;
int u,v;
for(int i=0;i<m;i++){
cin>>u>>v;
edge[u][v]=1;
}
cout<<Hungarin()<<'\n';
return 0;
}