数据结构与算法学习笔记----染色法判定二分图
@@ author: 明月清了个风
@@ first publish time: 2024.12.21ps⭐️其实就是个DFS,注释很详细,可以看一下注释
二分图
二分图的特点是:图中的顶点集可以被分成两个不相交的子集,并且图中的每一条边都连接这两个子集中的一个顶点。换句话说,图的所有边都仅连接两个子集中的顶点,两个子集内部的顶点之间没有边,所有的边都用于连接分别在两个集合中的两点。
没有奇数环:一个图是二分图当且仅当图中不存在奇数环。
证明:
假设有一张图 G G G中包含一个回路 C C C,其长度为奇数,设其长度为 2 k + 1 2k+1 2k+1,那么回路中有 2 k + 1 2k+1 2k+1个顶点。
从任一节点为起点开始染色:第一个顶点染色为 1 1 1,第二个顶点染色为 2 2 2。。。。。,以此类推,直到回路的最后一个顶点。因为回路的长度为 2 k + 1 2k+1 2k+1,所以经过 2 k + 1 2k+1 2k+1步回到起点,最终起点会被重新染色为 2 2 2,与开始时矛盾,因此一张图是二分图,不可能含有长度为奇数的回路。
染色法
所谓的染色法其实就是一个图的遍历问题,在遍历的过程中对每个顶点的颜色进行记录就可以了,需要注意的就是检查冲突。
这里我们使用的是DFS遍历,当然BFS也可以。
Acwing 860. 染色法判定二分图
[原题链接](860. 染色法判定二分图 - AcWing题库)
给定一个 n n n个点 m m m条边的有向图,图中可能存在重边和自环
请你判断这个图是否是二分图
输入格式
第一行包含三个整数 n n n, m m m。
接下来 m m m行,每行包含两个个整数 u u u、 v v v,表示存在一条从点 u u u到点 v v v的有向边
输出格式
如果给定图是二分图,则输出 Yes
,否则输出 No
。
数据范围
1 ≤ n , m ≤ 1 0 5 1 \leq n,m \leq 10^5 1≤n,m≤105,
代码
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 100010, M = N * 2;
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; // 将点染色为c,c的取值为1或2
for(int i = h[u]; i != -1; i = ne[i]) // 遍历该点所在的连通块
{
int j = e[i];
if(!color[j]) // 连通的点没有染色就染色
{
if(!dfs(j, 3 - c)) return false; // 染色失败返回false
}
else if(color[j] == c) return false; // 染色冲突返回false
}
return true;
}
int main()
{
cin >> n >> m;
memset(h, -1, sizeof h); // 初始化表头
for(int i = 0; i < m; i ++) // 读入所有边
{
int a, b;
cin >> a >> b;
add(a, b), add(b, a);
}
int flag = true;
for(int i = 1; i <= n; i ++) // 遍历所有点,因为图中可能会有不连通的点集
{
if(!color[i]) // 如果还没有染色, 就染色
{
if(!dfs(i, 1)) // 判断是否染色成功
{
flag = false; //失败将flag置0
break;
}
}
}
if(flag) puts("Yes");
else puts("No");
return 0;
}