染色法用于判断是否是二分图。
算法原理
-
二分图:
结点由两个集合组成,且两个集合内部没有边的图。
-
二分图一定不含奇数环:
因为每一条边都是从一个集合走到另一个集合,只有走偶数次才可能回到同一个集合。 -
不含奇数环的一定是二分图:
- 染色法:当一个点染上黑色,与它相邻的点必须染上白色。同理,染上白色的点,其相邻点必须染上黑色。
- 由于不存在奇数环,因此染色过程使不存在矛盾的。
- 只需证其逆否命题成立:如果在染色过程中出现了矛盾,这个环是奇数环。
- 染色出现矛盾时,一定是某个点染完色,染它相邻的点是发现这个点已经被染过颜色了,并且和这个点颜色相同。
- 因为染色一定是一黑一白,那么出现两个相同颜色时,将黑白看成一组,一定是n组再加上一个独立的点,即点数是2n+1,这个环是奇数环。
由上面的性质,可以使用 DFS 或者 BFS 来遍历这张图。如果发现了奇环(染色法),那么就不是二分图,否则是。
遍历所有的点和边,时间复杂度是 O ( m + n ) O(m+n) O(m+n)
算法实现
#include <iostream>
#include <cstring>
using namespace std;
const int N = 100005, M = 200005;
int n, m;
int head[N], lnk[M], nxt[M], idx;
int color[N];
bool flag = true;
void add(int u, int v) {
lnk[idx] = v;
nxt[idx] = head[u];
head[u] = idx ++ ;
}
void dfs(int u, int c) {
color[u] = c;
for (int i = head[u]; i != -1; i = nxt[i]) {
int v = lnk[i];
if (!color[v]) dfs(v, -c); // v没被染色
else if (color[v] == c) flag = false;
}
}
int main() {
scanf("%d%d", &n, &m);
memset(head, -1, sizeof head);
int u, v;
for (int i = 1; i <= m; i ++ ) {
scanf("%d%d", &u, &v);
add(u, v); add(v, u);
}
for (int i = 1; i <= n; i ++ ) {
if (!color[i]) dfs(i, 1);
}
if (flag) printf("Yes\n");
else printf("No\n");
}