寒假Day9:二分图的判断+匈牙利算法
二分图匹配相关概念
给定一个二分图G,在G的一个子图M中,M的边集{E}中的任意两条边都不依附于同一个顶点,则称M是一个匹配。
极大匹配是指在当前已完成的匹配下,无法再通过增加未完成匹配的边的方式来增加匹配的边数。
最大匹配是所有极大匹配当中边数最大的一个匹配。选择这样的边数最大的子集称为图的最大匹配问题。
如果一个匹配中,图中的每个顶点都和图中某条边相关联,则称此匹配为完全匹配,也称作完备匹配。
求解算法:求二分图匹配可以用最大流(Maximal Flow)或者匈牙利算法。
注意:完全匹配一定是极大匹配,但是极大匹配不一定是完全匹配。
交替路:从一个未匹配的点出发,依次经过未匹配边、匹配边、未匹配边....这样的路叫做交替路。
增广路:从一个未匹配的点出发,走交替路,到达了一个未匹配过的点,这条路叫做增广路。
看下图,其中1、4、5、7是已经匹配的点,1->5,4->7是已经匹配的边,那么我们从8开始出发,8->4->7->1->5->2这条路就是一条增广路。
二分图的判断
对于二分图的问题我们首先要判断一个图它是不是二分图。
对于二分图的判断方法最常见的是染色法(对每一个点进行染色操作,我们只用黑白两种颜色,问能不能使所有的点都染上了色,而且相邻两个点的颜色不同,如果可以那么这个图就是一个二分图)。
对于判断是否是一个二分图的方法可以用dfs和bfs两种方式去实现。
bfs判断二分图:
1 bool bfs()//用bfs 判断一幅图是否是二分图 2 { 3 memset(col,0,sizeof(col)); 4 //染色后的状态只有1和-1 5 //所以染色的初始状态为全部清空为0 6 queue<int> Q; 7 Q.push(1);//先让第一个点入队 8 col[1]=1;//给第一个点染色为1 9 while(!Q.empty()) 10 { 11 int p=Q.front(); 12 Q.pop(); 13 for(int i=0; i<v[p].size(); i++) 14 { 15 //找与第一个点相邻的点 16 int x=v[p][i]; 17 if(col[x]==0)//如果未被染色 18 { 19 col[x]=-col[p];//就染成与当前点相反的颜色 20 Q.push(x); 21 } 22 else//如果已经被染色了且和当前节点染色相同,则确定不是二分图 23 { 24 if(col[p]==col[x]) 25 return 0; 26 } 27 } 28 } 29 return 1; 30 }
匈牙利算法
时间复杂度:O(n*m)
匈牙利算法模板:
1 bool dfs(int x) 2 { 3 for(int i=0; i<v[x].size(); i++) 4 { 5 int s=v[x][i]; 6 if(book[s]==0) // 判断是否标记过,没有被标记进行下一步 7 { 8 book[s]=1; 9 if(f[s]==-1||dfs(f[s])) // 如果没有被匹配过且能和其他的点进行匹配 10 { 11 f[s]=x; 12 return 1; 13 } 14 } 15 } 16 return 0; 17 }
补充:
- 匈牙利算法得到的最大匹配并不是唯一的,预设匹配边、或者匹配顺序不同等,都可能会导致有多种最大匹配情况,所以有一种替代KM算法的想法是,我们只需要用匈牙利算法找到所有的最大匹配,比较每个最大匹配的权重,再选出最大权重的最优匹配即可得到更贴近真实情况的匹配结果。但这种方法时间复杂度较高,会随着目标数越来越多,消耗的时间大大增加,实际使用中并不推荐。
- 匈牙利算法是最简单最常见的求最大匹配数的算法,但是它的时间复杂度是O(n*m),对于一般的题来说最多有500个点,所以匈牙利是最好的做法。
- 假如遇到点数更多的话,可以使用Hopcroft-Karp算法,它的时间复杂度是O(n^(1/2) * m)。
- 以后碰到更深的题目再去学习一下HK算法、KM算法(带权二分图的最优匹配问题)、匈牙利算法求二分图多重匹配。
小知识点:
- Have all the workers punched in yet?
所有工人上班都打卡了吗? punch in 打卡 ~on time - abs是求一个整数的绝对值,fabs是求一个实数的绝对值
- 数组开小了不会显示RE,会提示超时
可参考博客:
https://blog.csdn.net/dark_scope/article/details/8880547