算法学习笔记(31)——染色法判定二分图
染色法判定二分图
如果一张无向图的 \(N\) 个节点(\(N \geq 2\))可以分成 \(A,B\) 两个非空集合,其中 \(A \cap B = \emptyset\),并且在同一集合内的点之间都没有边相连,那么称这张无向图为一张二分图。\(A,B\) 分别称为二分图的左部和右部。
定理:
一张无向图是二分图,当且仅当图中不存在奇环(长度为奇数的环)。
根据上述定理,我们可以用染色法进行二分图的判定。染色法的基本思路是:尝试用黑白两种颜色标记图中的节点,当一个节点被标记之后,它的所有相邻节点应该被标记为与它相反的颜色。若标记过程中产生冲突,则说明图中存在奇环。二分图染色一般基于深度优先遍历实现,时间复杂度为 \(O(N+M)\)。
在代码实现中,由于染色过程中需要快速知道某个节点的相邻节点,所以采用邻接表存储图;由于是无向图,所以需要每次存双向边。
color[]
数组表示当前节点的颜色,0
表示未染色,1
与2
是两种颜色,染色时可用3-c
表示与c
相反的颜色。
#include <iostream>
#include <cstring>
using namespace std;
const int N = 1e5 + 10, M = N * 2;
int n, m;
// 邻接表存储无向图,注意无向图需要对边相关的数组开两倍的内存空间
int h[N], e[M], ne[M], idx;
// 染色数组,0代表未染色,1和2表示两种颜色
int color[N];
void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
}
// 将节点u染为c色,并检查其相邻节点
bool dfs(int u, int c)
{
// 将u染为c色
color[u] = c;
// 遍历相邻节点
for (int i = h[u]; i != -1; i = ne[i]) {
int j = e[i];
// 如果当前节点还没有染色
if (!color[j]) {
// 将j染为相反的颜色,若有冲突则返回false,染色失败
if (!dfs(j, 3 - c)) return false;
}
// 若当前节点已经染色,则判断相邻节点颜色是否相同,若相同则返回false,染色失败
else if (color[j] == c) return false;
}
// 整个连通块都能成功染色,返回true
return true;
}
int main()
{
// 初始化邻接表
memset(h, -1, sizeof h);
cin >> n >> m;
while (m -- ) {
int u, v;
cin >> u >> v;
// 无向图每次添加双向边
add(u, v), add(v, u);
}
// flag记录是否为二分图,默认为true
bool flag = true;
// 循环处理每一个节点所在的连通块
for (int i = 1; i <= n; i ++ )
// 如果当前节点没有染色,说明是一个新的连通块
if (!color[i]) {
// 用染色法进行染色,若染色失败,则该图不是二分图,跳出循环
if (!dfs(i, 1)) {
flag = false;
break;
}
}
if (flag) puts("Yes");
else puts("No");
return 0;
}