目录
一、什么是二分图?
二分图又称作二部图,是图论中的一种特殊模型。 设G=(V,E)是一个无向图,如果顶点V可分割为两个互不相交的子集(A,B),并且图中的每条边(i,j)所关联的两个顶点i和j分别属于这两个不同的顶点集(i in A,j in B),则称图G为一个二分图。
即所有点被划分为两个集合,所有的边连接存在不同集合中的点;每个集合中不存在边
二分图中当且仅当不存在奇数环
二、染色法
染色法用于判断一个图是不是二分图;由于二分图中不存在奇数环,所以染色过程不存在矛盾;
对于每一个顶点若确定了颜色,那么便可确定整张图的所有顶点的颜色;且每条边的两个顶点的颜色一定不同。
核心思想:dfs判断以当前节点为根的子树中是否存在矛盾
模板题:
链接:https://www.acwing.com/problem/content/862/
/**
* 染色法:判断一个图是不是二分图
* dfs的方式每次染色以当前节点为根的子树,只要发生矛盾,则不是二分图
*/
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int N = 100010;
int n, m;
int e[N * 2], ne[N * 2], h[N], idx;
int match[N]; // 记录染色的数字
void add(int a, int b) {
e[idx] = b;
ne[idx] = h[a];
h[a] = idx ++;
}
bool dfs(int x, int color) {
match[x] = color;
for (int i = h[x]; i != -1; i = ne[i]) {
int j = e[i];
if (!match[j]) {
if(!dfs(j, 3 - color)) return false;
} else if (match[j] == color) return false;
}
return true;
}
int main()
{
memset(h, -1, sizeof h);
cin >> n >> m;
while (m --) {
int a, b;
cin >> a >> b;
add(a, b);
add(b, a);
}
// 遍历集合,看是否会发生矛盾
bool flag = true;
for (int i = 1; i <= n; i++) {
if (!match[i]) {
// dfs含义:对以i为根的字数进行染色,其中顶点i的颜色为1
if (!dfs(i, 1)) {
flag = false;
break;
}
}
}
if (flag) puts("Yes");
else puts("No");
return 0;
}
三、匈牙利算法
判断二分图最大匹配。时间复杂度最坏O(n * m);但实际远小于这个理论
二分图的匹配:给定一个二分图 GG,在 GG 的一个子图 MM 中,MM 的边集 {E}{E} 中的任意两条边都不依附于同一个顶点,则称 MM 是一个匹配。
二分图的最大匹配:所有匹配中包含边数最多的一组匹配被称为二分图的最大匹配,其边数即为最大匹配数。
算法思路:将二分图中的两个集合看做男生和女生,进行匹配。假设只遍历男生集合,选择第一个女生与其匹配;如果选择的女生已经被选,那么试着改变女生匹配的男生换一个人选;否则只能放弃
因此,在构建图的时候只需要保证一个方向的边就行,即有向图
模板题:
链接:https://www.acwing.com/problem/content/863/
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int N = 510, M = 100010;
int e[M], ne[M], h[N], idx;
int match[N]; // 存储匹配的男生
bool vis[N]; // 标记不能重复选择一个女生
void add(int a, int b) {
e[idx] = b;
ne[idx] = h[a];
h[a] = idx ++;
}
bool find(int x) {
for (int i = h[x]; i != -1; i = ne[i]) {
int j = e[i];
if (!vis[j]) {
vis[j] = true;
if (match[j] == 0 || find(match[j])) {
match[j] = x;
return true;
}
}
}
return false;
}
int main()
{
memset(h, -1, sizeof h);
int n1, n2, m;
scanf("%d%d%d", &n1, &n2, &m);
while (m --) {
int a, b;
scanf("%d%d", &a, &b);
add(a, b);
}
// 遍历男生集合
int res = 0;
for (int i = 1; i <= n1; i++) {
memset(vis, false, sizeof vis);
if(find(i)) { // 寻找i匹配的点
res ++;
}
}
printf("%d\n", res);
return 0;
}
总结
刚接触二分图,记录下学习到的两个算法