0
点赞
收藏
分享

微信扫一扫

二分图及其判定丶匈牙利算法求最大匹配


二分图

在​​图论​​中,二分图是一类特殊的​​图​​,又称为双分图二部图偶图。二分图的顶点可以分成两个互斥的​​独立集​​​ U 和 V 的图,使得所有边都是连结一个 U 中的点和一个 V 中的点。顶点集 U、V 被称为是图的两个部分。等价的,二分图可以被定义成图中所有的​​环​​都有偶数个顶点 。(来源维基百科)

如果某个图为二分图,那么它至少有两个顶点,且其所有回路的长度均为偶数,任何无回路的的图均是二分图。

二分图及其判定丶匈牙利算法求最大匹配_#include

下面参考图 :  ​​javascript:void(0)​​ 

二分图及其判定丶匈牙利算法求最大匹配_二分图_02

见上图 所示,其存在回路。如:1-4-2-5-1,长度为4,偶数。任意一种都为偶数,证明略。如果在1和2之前添一条边,那就不是二分图了,如下图。 

二分图及其判定丶匈牙利算法求最大匹配_最大匹配_03

  添了1--2的边后,回路就存在了1--4--2--1,长度为3,奇数,所以图3就不是二分图。

定理 :    一张无向图是二分图,当且仅当图中不存在长度为奇数的环 。

根据该定理, 我们可以用染色法进行二分图的判定。大致思想是 : 尝试用黑白两种颜色标记图中的节点, 当一个节点被标记后,它的所有相邻节点应该被标记与它相反的颜色。若标记过程中产生冲突,则说明图中存在奇环。

伪代码: 

void dfs(int x ,int color)
赋值 v[x] <- color
对于与 x 顶点相邻的边 y ,
if(v[y] == 0 ) then
dfs(y, -color )
else if(v[y] ≠ color)
判断无向图不是二分图,算法结束
int main (){
for i <- 1 to N
if(v[i] == 0 ) then dfs(i,1)
判断无向图是二分图

代码 :

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <queue>
#include <algorithm>
using namespace std ;
const int MAX = 100 ;
typedef long long LL ;
int m , n ; // m 边 , n 顶点
int color[MAX] ;
int edge[MAX][MAX] ;
bool dfs(int v ,int c) {
color[v] = c ; // 把当前节点染色
for(int i = 0; i<n ; i++) {
if(edge[v][i]) { // 遍历相邻的顶点
if(color[i] == c ){
return false ; // 产生冲突
}
if(color[i] == 0){//如果还未涂色,就染上相反的颜色-c,并dfs这个顶点,进入下一层
if(!dfs(i,-c)){
return false ;
}
}
}
}
return true ;

}
void solve(){
for(int i = 0 ; i<n ; i++ ) {
if(color[i] == 0 ) {
if(!dfs(i,1)){
cout<<"Not"<<endl ; // 不是二分图
return ;
}
}
}
cout<<"Yes"<<endl ; // 是二分图
}
int main(){
while(cin >>n >>m ){
memset(color,0,sizeof(color)) ;
memset(edge,0,sizeof(edge)) ;
for(int i = 0 ; i<m ; i++ ) {
int u , v ;
cin >> u >> v ;
edge[u][v] = 1 ;
edge[v][u] = 1 ;
}
solve() ; // 判断是否是二分图
}



return 0 ;
}

二分图匹配

给定一个二分图G,在G的一个子图M中,M的边集{E}中的任意两条边都不依附于同一个顶点,则称M是一个匹配 (匹配是一个边的集合)。 
图中加粗的边是数量为2的匹配。 

二分图及其判定丶匈牙利算法求最大匹配_二分图_04

 我们定义匹配点匹配边未匹配点非匹配边,它们的含义非常显然。例如图 3 中 1、4、5、7 为匹配点,其他顶点为未匹配点;1-5、4-7为匹配边,其他边为非匹配边。

                                                 

二分图及其判定丶匈牙利算法求最大匹配_二分图_05

             

二分图及其判定丶匈牙利算法求最大匹配_#include_06

最大匹配:一个图所有匹配中,所含匹配边数最多的匹配,称为这个图的最大匹配。图 4 是一个最大匹配,它包含 4 条匹配边,而图三只有2条匹配边。

完美匹配:如果一个图的某个匹配中,所有的顶点都是匹配点,那么它就是一个完美匹配。图 4 是一个完美匹配。显然,完美匹配一定是最大匹配(完美匹配的任何一个点都已经匹配,添加一条新的匹配边一定会与已有的匹配边冲突)。但并非每个图都存在完美匹配。

二分图及其判定丶匈牙利算法求最大匹配_最大匹配_07

交替路 : 从一个未匹配点出发,依次经过非匹配边、匹配边、非匹配边...形成的路径叫交替路。

(增广路)交错路: 如果在二分图中存在一条连接两个非匹配点的路径 path  , 使得非匹配边与匹配边在 path 上交替出现, 那么称 path 是匹配 (边的集合) 的增广路 。

例如,图 5 中的一条增广路如图 6 所示(图中的匹配点均用红色标出):

二分图及其判定丶匈牙利算法求最大匹配_最大匹配_08

增广路显然具有以下性质 : 

          1 . 长度 len  是奇数 . 

          2 . 路径上的第 1 , 3 , 5 , .... len , 条边是非匹配边 , 第 2 , 4 , 6 .... len -1  条边是匹配边 。

          正是因为以上的性质,如果我们把路径上的所有边的状态取反,原来的匹配边变成非匹配边 , 原来非匹配边变成 匹配边, 那么

         我们就可以得到新的边集 仍然是一组匹配,并且匹配边数增加了1 。

          进一步可以得到推论 : 

          二分图的一组最大匹配 S 是 最大匹配 ,当且仅当图中不存在 S 的增广路 . 
 

  匈牙利算法(增广路算法) 

  主要的过程是 :

 1 . 设 S = 

二分图及其判定丶匈牙利算法求最大匹配_#include_09

 , 即所有的边都是非匹配边 . 

 2 . 寻找增广路 path , 把路径上所有边的状态取反 ,得到一个更大的匹配 S'  . 

 3 . 重复第二步 , 直到图中不存在增广路 . 

该算法的关键在于如何找到一条增广路。匈牙利算法依次尝试给每一个左部节点 x 寻找一个匹配的右部节点 y . 右部点 y 能与左部点 x 匹配 ,需要满足以下两个条件之一 : 

 1  y 本身就是非匹配点  

 2  y 已经和左部边另一个点 x' 匹配 ,但从 x' 出发能找到另一个右部点 y' 与之匹配. 

    此时路径  x ~y ~x' ~ y~  为一条增广路 . 

 

寻找增广路

 

二分图及其判定丶匈牙利算法求最大匹配_最大匹配_10

 

红边为三条已经匹配的边。从X部一个未匹配的顶点x4开始,找一条路径: 

x4 -> y3-> x2->y1->x1->y2 

因为y2是Y部中未匹配的顶点,故所找路径是增广路径。 

其中有属于匹配M的边为{x2,y3},{x1,y1} 

不属于匹配的边为{x4,y3},{x2, y1}, {x1,y2} 

可以看出:不属于匹配的边要多一条! 

二分图及其判定丶匈牙利算法求最大匹配_#include_11

 

如果从M中抽走{x2,y3},{x1,y1},并加入{x4,y3},{x2, y1}, {x1,y2},也就是将增广路所有的边进行”反色”,则可以得到四条边的匹配M’={{x3,y4}, {x4,y3},{x2, y1}, {x1,y2}} 

容易发现这样修改以后,匹配仍然是合法的,但是匹配数增加了一对。另外,单独的一条连接两个未匹配点的边显然也是交错轨.可以证明,当不能再找到增广轨时,就得到了一个最大匹配.这也就是匈牙利算法的思路. 

可知四条边的匹配是最大匹配.

找增广路径的算法

我们采用DFS的办法找一条增广路径: 
从X部一个未匹配的顶点u开始,找一个未访问的邻接点v(v一定是Y部顶点)。对于v,分两种情况:

1 如果v未匹配,则已经找到一条增广路 . 
2 如果v已经匹配,则取出v的匹配顶点w(w一定是X部顶点),边(w,v)目前是匹配的,根据“取反”的想法,要将(w,v)改为未匹配,    (u,v)设为匹配,能实现这一点的条件是看从w为起点能否新找到一条增广路径P’。如果行,则u-v-P’就是一条以u为起点的增广路   径。
 

P3386 【模板】二分图匹配

题目描述

给定一个二分图,结点个数分别为n,m,边数为e,求二分图最大匹配数

输入输出格式

输入格式:

 

第一行,n,m,e

第二至e+1行,每行两个正整数u,v,表示u,v有一条连边

 

输出格式:

 

共一行,二分图最大匹配

 

输入输出样例

输入样例#1: 复制

1 1 1
1 1

输出样例#1: 复制

1

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <queue>
#include <algorithm>
using namespace std ;
const int MAX = 1005 ;
typedef long long LL ;
int cx[MAX] , cy[MAX] ;
int vis[MAX] ;
int e[MAX][MAX];
int ans ;
int m ; // 边数
int nx ,ny ; // 左部顶点个数 ,右部顶点个数 .
bool dfs(int u){
for(int v = 1 ; v<=ny ; v++) { // 遍历右部顶点集
if(e[u][v]&&!vis[v]){ // 如果u 到 v 有边 ,且没访问过
vis[v] = 1 ;
if(cy[v] == -1 || dfs(cy[v])){
// 如果v没有匹配,则已经找到一条增广路,结束算法
// 否则 继续 dfs(它的匹配点)
cx[u] = v ;
cy[v] = u ;
return true ;
}
}
}
return false ;
}
void Maxmatch(){ // 二分图最大匹配

memset(cx,-1,sizeof(cx)) ;
memset(cy,-1,sizeof(cy)) ; // 初始化为都是分匹配
for(int i = 1 ; i<= nx ; i ++) { // 遍历二分图左边顶点
if(cx[i] == -1) {//如果左边的i顶点还没有匹配的点,就对i顶点进行匹配
memset(vis,0,sizeof(vis)) ;
ans+=dfs(i) ;
}
}
}
int main(){
cin >> nx >> ny >> m ;
for(int i = 1 ; i<=m ;i++ ) {
int x , y ;
scanf("%d%d",&x,&y);

e[x][y] = 1 ;

}
Maxmatch() ;
cout<<ans<<endl;



return 0 ;
}


 

 

 

举报

相关推荐

0 条评论