主要内容:
染色法判定二分图
-
二分图:把无向图分为两个集合V1, V2,所有边都在V1和V2之间,V1或V2内部没有边。一个图是否为二分图,一般用"染色法"判断。
例如:
(1、2在一个集合,3,4在一个集合) -
染色法:用两种颜色(可以赋值为1、2)对所有顶点进行染色,要求一条边所连的两个相邻顶点的颜色不同,颜色结束后,若所有相邻顶点的颜色都不相同,就是二分图。
二分图一定不是奇数环(如下图,奇数环存在相邻两个点颜色相同的情况,说明它们在一个集合内)
我们可以用DFS来实现:
代码思路:
- 用1、2表示不同颜色,0表示未染色
- dfs遍历所有点。将未染色的点进行染色
- 如果当前点未染色,染成另一种颜色,染色失败返回false,如果当前点染过色但与邻点撞色,说明两点在同一集合.
bool dfs(int u, int c)
{
color[u] = c;
for(int i = h[u]; i != -1; i = ne[i])
{
int j = e[i];
if(!color[j])//没染过色, 染成另外一种颜色
{
if(!dfs(j, 3 - c)) return false;//未染成另一种颜色
}
else if(color[j] == c) return false;//染过色,但邻点撞色
}
return true;//未发生矛盾
}
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 100010, M = 200010;//因为是双向边所以m扩大
int n, m;
int h[N], e[M], ne[M], idx;
int color[N];
void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}
bool dfs(int u, int c)
{
color[u] = c;
for(int i = h[u]; i != -1; i = ne[i])
{
int j = e[i];
if(!color[j])//没染过色, 染成另外一种颜色
{
if(!dfs(j, 3 - c)) return false;
}
else if(color[j] == c) return false;//染过色,但邻点撞色
}
return true;//未发生矛盾
}
int main()
{
cin >> n >> m;
memset(h, - 1, sizeof h);//初始化邻接表
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(!color[i])//未被染色
{
if(!dfs(i, 1))//如果染1有矛盾发生
{
flag = false;//不是二分图
break;
}
}
if(flag) puts("Yes");
else puts("No");
return 0;
}
复杂度O(n + m)
匈牙利算法
使用匈牙利算法求二分图的最大匹配数
二分图的匹配:给定一个二分图G,在G的一个子图M中,M的边集{E}中的任意两条边都不依附于同一个顶点,则称M是一个匹配。
二分图的最大匹配:所有匹配中包含边数最多的一组匹配被称为二分图的最大匹配,其边数即为最大匹配数。
算法分析:
图中点3条红线表示匹配成功,匹配的过程分析:
类似于男女匹配,男的先匹配第一次遇见的姑娘,然后往下看另一个男生,如果两人喜欢同一个姑娘,那这个姑娘如何处理?1号姑娘观察1号男生还喜欢2号女生,为了形成一个皆大欢喜的局面,男1女2匹配,男2女1匹配。3号男生虽然也喜欢2号女生,不过人家有对象了,还好他没吊死在一棵树上,匹配3号女生。
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 510, M = 100010;
int n1, n2, m;
bool st[N];
int h[N], e[M], ne[M], idx;
int match[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(!st[j])
{
st[j] = true;
if(match[j] == 0 || find(match[j]))//姑娘未匹配, 或者与姑娘匹配的男生可以选择另一个妹子
{
match[j] = x;//姑娘与x匹配
return true;//匹配成功
}
}
}
return false;
}
int main()
{
cin >> n1 >> n2 >> m;
memset(h, -1, sizeof h);
while(m -- )
{
int a, b;
cin >> a >> b;
add(a, b);
}
int res = 0;
for(int i = 1; i <= n1; i ++ )
{
memset(st, false, sizeof st);//清空所有姑娘
if(find(i)) res ++ ;
}
cout << res << endl;
return 0;
}
复杂度分析:
考虑每个男生(共n个),最坏情况下遍历所有边(m条),最坏情况下是O(nm),实际情况是并不需要遍历完所有边,所以复杂度远小于O(nm).