本课时有配套视频讲解,购买后即可观看(永久有效)
二分图判定
一、课上练习
编程练习
- 判定二分图:L49994
二、知识总结
✨ 核心思想
二分图(Bipartite Graph)是指图的顶点可以被分成两个不相交的集合 和 ,使得图中的每条边都连接 中的一个顶点和 中的一个顶点(即同一集合内的顶点之间没有边)。
判定一个图是否为二分图的方法是 2-着色法:尝试用两种颜色对图进行着色,使得每条边两端的顶点颜色不同。如果能成功着色,则图是二分图;否则不是。
等价定理:一个图是二分图,当且仅当它不包含奇数长度的环。
✨ 算法原理
BFS 2-着色法
- 选择一个未着色的顶点,染为颜色 0,加入队列。
- BFS 遍历:取出队首顶点 ,对所有邻居 :
- 如果 未着色,染为 的反色(),入队。
- 如果 已着色,且颜色与 相同,则发现冲突——图不是二分图。
- 由于图可能不连通,需要对所有连通分量重复此过程。
DFS 2-着色法
与 BFS 类似,但使用递归 DFS 进行遍历:
- DFS 到顶点 时,对所有邻居 :
- 未着色 → 染反色并递归。
- 已着色且同色 → 不是二分图。
两种方法时间复杂度相同,均为 。
✨ 代码实现
1#include <bits/stdc++.h>
2using namespace std;
3
4const int MAXN = 200005;
5
6vector<int> adj[MAXN]; // 邻接表
7int color[MAXN]; // -1=未着色, 0=颜色A, 1=颜色B
8int n, m;
9
10bool bfs(int start) {
11 queue<int> q;
12 color[start] = 0; // 起始顶点染为颜色0
13 q.push(start);
14
15 while (!q.empty()) {
16 int u = q.front();
17 q.pop();
18
19 for (int v : adj[u]) {
20 if (color[v] == -1) {
21 // 未着色,染为u的反色
22 color[v] = 1 - color[u];
23 q.push(v);
24 } else if (color[v] == color[u]) {
25 // 颜色冲突,不是二分图
26 return false;
27 }
28 }
29 }
30 return true;
31}
32
33int main() {
34 ios::sync_with_stdio(false);
35 cin.tie(nullptr);
36
37 cin >> n >> m;
38 memset(color, -1, sizeof(color));
39
40 for (int i = 0; i < m; i++) {
41 int u, v;
42 cin >> u >> v;
43 adj[u].push_back(v);
44 adj[v].push_back(u); // 无向图
45 }
46
47 bool isBipartite = true;
48 for (int i = 1; i <= n; i++) {
49 if (color[i] == -1) {
50 // 新的连通分量
51 if (!bfs(i)) {
52 isBipartite = false;
53 break;
54 }
55 }
56 }
57
58 if (isBipartite)
59 cout << "YES" << endl;
60 else
61 cout << "NO" << endl;
62
63 return 0;
64}1#include <bits/stdc++.h>
2using namespace std;
3
4const int MAXN = 200005;
5
6vector<int> adj[MAXN];
7int color[MAXN]; // -1=未着色, 0/1=两种颜色
8int n, m;
9
10bool dfs(int u, int c) {
11 color[u] = c;
12
13 for (int v : adj[u]) {
14 if (color[v] == -1) {
15 // 未着色,尝试染反色
16 if (!dfs(v, 1 - c)) return false;
17 } else if (color[v] == c) {
18 // 同色冲突
19 return false;
20 }
21 }
22 return true;
23}
24
25int main() {
26 ios::sync_with_stdio(false);
27 cin.tie(nullptr);
28
29 cin >> n >> m;
30 memset(color, -1, sizeof(color));
31
32 for (int i = 0; i < m; i++) {
33 int u, v;
34 cin >> u >> v;
35 adj[u].push_back(v);
36 adj[v].push_back(u);
37 }
38
39 bool isBipartite = true;
40 for (int i = 1; i <= n; i++) {
41 if (color[i] == -1) {
42 if (!dfs(i, 0)) {
43 isBipartite = false;
44 break;
45 }
46 }
47 }
48
49 cout << (isBipartite ? "YES" : "NO") << endl;
50
51 return 0;
52}1#include <bits/stdc++.h>
2using namespace std;
3
4const int MAXN = 200005;
5
6vector<int> adj[MAXN];
7int color[MAXN];
8int n, m;
9
10bool bfs(int start) {
11 queue<int> q;
12 color[start] = 0;
13 q.push(start);
14
15 while (!q.empty()) {
16 int u = q.front(); q.pop();
17 for (int v : adj[u]) {
18 if (color[v] == -1) {
19 color[v] = 1 - color[u];
20 q.push(v);
21 } else if (color[v] == color[u]) {
22 return false;
23 }
24 }
25 }
26 return true;
27}
28
29int main() {
30 ios::sync_with_stdio(false);
31 cin.tie(nullptr);
32
33 cin >> n >> m;
34 memset(color, -1, sizeof(color));
35
36 for (int i = 0; i < m; i++) {
37 int u, v;
38 cin >> u >> v;
39 adj[u].push_back(v);
40 adj[v].push_back(u);
41 }
42
43 bool ok = true;
44 for (int i = 1; i <= n; i++) {
45 if (color[i] == -1 && !bfs(i)) {
46 ok = false;
47 break;
48 }
49 }
50
51 if (!ok) {
52 cout << "NOT BIPARTITE" << endl;
53 return 0;
54 }
55
56 // 输出两个集合
57 vector<int> setA, setB;
58 for (int i = 1; i <= n; i++) {
59 if (color[i] == 0) setA.push_back(i);
60 else setB.push_back(i);
61 }
62
63 cout << "Set A (" << setA.size() << "): ";
64 for (int v : setA) cout << v << " ";
65 cout << endl;
66
67 cout << "Set B (" << setB.size() << "): ";
68 for (int v : setB) cout << v << " ";
69 cout << endl;
70
71 return 0;
72}✨ 执行示例
例1:二分图
图:6 个顶点,7 条边。
边:1-2, 1-4, 2-3, 3-4, 3-6, 4-5, 5-6
BFS 着色过程(从顶点1开始):
| 步骤 | 取出 u | color[u] | 邻居着色 |
|---|---|---|---|
| 1 | 1 | 0 | color[2]=1, color[4]=1 |
| 2 | 2 | 1 | color[3]=0 |
| 3 | 4 | 1 | 3已着色(0!=1 ok), color[5]=0 |
| 4 | 3 | 0 | 4已着色(1!=0 ok), color[6]=1 |
| 5 | 5 | 0 | 6已着色(1!=0 ok) |
| 6 | 6 | 1 | 无未着色邻居 |
无冲突。集合 A = {1, 3, 5}(颜色0),集合 B = {2, 4, 6}(颜色1)。是二分图。
例2:非二分图
图:3 个顶点,3 条边(三角形)。
边:1-2, 2-3, 1-3
着色:color[1]=0 → color[2]=1 → color[3]=0 → 检查边 1-3:color[1]=0 == color[3]=0,冲突!
三角形是奇环,不是二分图。
✨ 解题步骤详解
- 建图:读入无向图的边。
- 初始化颜色数组:全部设为 -1(未着色)。
- 遍历所有连通分量:对每个未着色的顶点启动 BFS/DFS。
- 2-着色判定:遇到同色冲突则不是二分图。
- 输出结果:根据题意输出"是/否"或两个集合。
二分图的常见应用:
- 二分图最大匹配(匈牙利算法):在二分图中找最大匹配数。
- 最小顶点覆盖(König 定理):最小顶点覆盖 = 最大匹配。
- 最大独立集 = - 最大匹配。
- 染色问题:判断是否可以用 2 种颜色着色。
- 分组问题:将元素分成两组,使得某些冲突关系不出现在同组中。
如何找到奇环:如果判定不是二分图,从冲突的边 出发,沿 BFS 树回溯到 LCA,即可找到奇环。
✨ 常见错误
- 忘记处理不连通的图:必须对所有连通分量都进行着色判定。
- 有向图直接用2-着色:二分图判定通常用于无向图。有向图需要先忽略方向(视为无向图)再判定。
- DFS 递归栈溢出:顶点数很大时,DFS 可能爆栈,建议用 BFS 或手动栈。
- 自环导致误判:含自环的图一定不是二分图(顶点与自身相连,颜色必然冲突)。
- color 数组初始化错误:必须初始化为 -1(或其他表示未着色的值),不能用 0。
- 重边不影响判定:重边不会影响二分图的判定结果。
✨ 算法评价
| 指标 | BFS 2-着色 | DFS 2-着色 |
|---|---|---|
| 时间复杂度 | ||
| 空间复杂度 | ||
| 实现难度 | 简单 | 简单 |
| 栈溢出风险 | 无 | 有( 大时) |
| 推荐程度 | 更推荐 | 适用于小图 |
二分图判定是图论的基础问题,2-着色法简洁高效。掌握它之后,可以进一步学习二分图匹配(匈牙利算法、Hopcroft-Karp)等更高级的内容。