题目
给定一个 $n$ 个点 $m$ 条边的无向图,图中可能存在重边和自环。
请你判断这个图是否是二分图。
输入格式
第一行包含两个整数 $n$ 和 $m$。
接下来 $m$ 行,每行包含两个整数 $u$ 和 $v$,表示点 $u$ 和点 $v$ 之间存在一条边。
输出格式
如果给定图是二分图,则输出 Yes
,否则输出 No
。
数据范围
$1 \le n,m \le 10^5$
输入样例:
4 4
1 3
1 4
2 3
2 4
输出样例:
Yes
题解
思路
染色法
-
二分图
一定不含奇数环(由奇数条边构成的环),二分图不一定是连通图,也就是说可能有多个组成部分 -
二分图
就是能将图内点分成两个集合,使得图内的边只出现在两个集合之间,就能证明他是二分图
利用染色法判定当前图是否是二分图的核心就是看当前图有没有奇数环
代码实现
- 利用DFS
- 利用1和2区分不同点的颜色,用0表示当前点未染色(也就是初始状态)
- 遍历所有的点将其初始染为1,利用dfs函数搜索与他同在一个连通块内的所有点,将其染色
- 如果当前点染过色了 就判断他当前的颜色与应该给他染的颜色是否矛盾,如果矛盾,break,标志染色失败
- 染色失败相当于相邻的两个点染的颜色相同
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10, M = 2e5 + 10;
int h[N], e[M], ne[M], idx;
int color[N];
int n, m;
void add(int a, int b)
{
e[idx] = b;
ne[idx] = h[a];
h[a] = idx ++ ;
}
bool dfs(int a, int y)
{
color[a] = y; //给当前点染色
for (int i = h[a]; i != -1; i = ne[i]) //遍历与当前点相连的点 每一次dfs内都执行将相连的点遍历就能将二分图内一个连通块搜索完
{
//cout << 1;
int j = e[i]; //给j赋值当前点a连接的点的编号
if (!color[j]) //如果当前点没有被涂过
{
if (!dfs(j, 3 - y)) //继续向下搜 3-y 是一个很巧妙的点 点只会被涂成1或2 当前点涂1下一个点就涂2 3-a就能确保相互连接的点被涂成不同颜色
{
return false; //如果往下搜的点返回了false 当前点返回false
}
}
else
{
if (color[j] != 3 - y) //如果当前点应该涂的颜色和当前点实际颜色不相符 证明该图不是二分图
{
return false;
}
}
}
return true;
}
void solve()
{
scanf("%d%d", &n, &m);
memset(h, -1, sizeof h);
while (m -- )
{
int a, b;
scanf("%d%d", &a, &b);
add(a, b);
add(b, a);
}
bool flag = true;
for (int i = 1; i <= n; i ++ )
{
if (!color[i]) //如果这个点没有被染过色 这个if判断是关键 我们只取搜没有被染过色的点 搜一个点 就可以将该点所在的连通图全部搜完了 如果不加这个if判断 连通块内的点会被重复搜索 上一次搜索该连通块时对块内点color[]赋的值会污染本次搜索 造成计算错误 所以不必重复搜索
{
if (!dfs(i, 1)) //从这个点开始搜索 将其染为1号颜色 如果dfs返回false 执行以下操作
{
flag = false;
break;
}
}
}
if (flag) printf("Yes\n");
else puts("No");
return;
}
int main()
{
solve();
return 0;
}