一、基本思路
1、基本概念
- 二分图:
-
- 二分图指的是讲一个图可以分成两部分(集合),每个集合内的定点之间不存在边,两个集合定点之间只存在两部分的边。
- 性质:
-
- 二分图 <<充要条件>> 图中不含有奇数环
-
- 图中不含有奇数环 ==>> 染色过程不会出现矛盾(可以用反证法进行证明)
2、算法步骤
-
染色过程:
-
-
- 从前向后遍历每一个点
-
-
-
- 若当前这个点并没有分好组的话,则默认为 1 分组
-
-
-
- 遍历与初始点连通的所有点 ==>> 染色为 2 分组(标准:一条边的两端染色必然不同,没有奇数环,则一定没有矛盾)
-
-
染色步骤:(判断一个图是否是二分图)
for(int i = 1; i <= n; i++){
if(i 未染色){
dfs(i, 1); // 前一个参数是需要染色的点,后一个参数是需要染的色
}
}
二、Java、C语言模板实现
static int N = 100010;
static int M = 2*N; // 此处是为了存储边的大小,因为是无向图,因此存的是两倍点的边数
static int n,m;
static int u,v;
static int idx = 0;
static int[] h = new int[N];
static int[] e = new int[M];
static int[] ne = new int[M];
static int[] color = new int[N]; // 存储 n 个点的染的颜色,0代表没有染过色,1,2 分别是所染的颜色
static void add(int a, int b){ // 单链表存储
e[idx] = b;
ne[idx] = h[a];
h[a] = idx++;
}
static boolean dfs(int x, int c){
color[x] = c; // 将点 x 进行染色
for(int i = h[x]; i != -1; i = ne[i]){ // 邻接表遍历每个可以到达的点
int newNode = e[i]; // 可以到达的点
if(color[newNode] == 0){ // 下一个点未被染色
if(dfs(newNode, 3 - c) == false){
// 没有染色我就递归进行染色操作,3 - c代表,若c为1,则将邻接点染成2,若c为2,则邻接点染为1
return false;
}
}else if(color[newNode] == c){ // 下一个点被染色了+同色
// 如果下一个点已经染色,并且和当前点相同,则代表存在奇数环,不可以构成二分图
return false;
}
}
return true;
}
```cpp
// C++语言实现,此处是yxc实现
int n; // n表示点数
int h[N], e[M], ne[M], idx; // 邻接表存储图
int color[N]; // 表示每个点的颜色,-1表示未染色,0表示白色,1表示黑色
// 参数:u表示当前节点,c表示当前点的颜色
bool dfs(int u, int c)
{
color[u] = c;
for (int i = h[u]; i != -1; i = ne[i])
{
int j = e[i];
if (color[j] == -1)
{
if (!dfs(j, !c)) return false;
}
else if (color[j] == c) return false;
}
return true;
}
bool check()
{
memset(color, -1, sizeof color);
bool flag = true;
for (int i = 1; i <= n; i ++ )
if (color[i] == -1)
if (!dfs(i, 0))
{
flag = false;
break;
}
return flag;
}
三、例题题解
// java题解实现
import java.util.*;
import java.io.*;
public class Main{
static int N = 100010;
static int M = 2*N; // 此处是为了存储边的大小,因为是无向图,因此存的是两倍点的边数
static int n,m;
static int u,v;
static int idx = 0;
static int[] h = new int[N];
static int[] e = new int[M];
static int[] ne = new int[M];
static int[] color = new int[N]; // 存储 n 个点的染的颜色,0代表没有染过色,1,2 分别是所染的颜色
static void add(int a, int b){ // 单链表存储
e[idx] = b;
ne[idx] = h[a];
h[a] = idx++;
}
static boolean dfs(int x, int c){
color[x] = c; // 将点 x 进行染色
for(int i = h[x]; i != -1; i = ne[i]){ // 邻接表遍历每个可以到达的点
int newNode = e[i]; // 可以到达的点
if(color[newNode] == 0){ // 下一个点未被染色
if(dfs(newNode, 3 - c) == false){
// 没有染色我就递归进行染色操作,3 - c代表,若c为1,则将邻接点染成2,若c为2,则邻接点染为1
return false;
}
}else if(color[newNode] == c){ // 下一个点被染色了+同色
// 如果下一个点已经染色,并且和当前点相同,则代表存在奇数环,不可以构成二分图
return false;
}
}
return true;
}
public static void main(String[] args) throws IOException {
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
//以后要熟练使用BufferedReader进行快读
String[] str1 = reader.readLine().split(" ");
Arrays.fill(h, -1);
n = Integer.parseInt(str1[0]);
m = Integer.parseInt(str1[1]);
for(int i = 0; i < m; i++){
String[] str2 = reader.readLine().split(" ");
u = Integer.parseInt(str2[0]);
v = Integer.parseInt(str2[1]);
add(u, v); // 无向图设置双边
add(v, u);
}
boolean flag = true; // 是否是二分图的标志/也可以看做是否存在奇数环标志
for(int i = 1; i <= n; i++){ // 遍历每个点
if(color[i] == 0){ // 判断是否已经染色
if(dfs(i, 1) == false){ // 如果判断是否是二分图+染色,如果不是二分图,那么就直接跳出循环输出
flag = false;
break;
}
}
}
if(flag == true){
System.out.println("Yes");
}else{
System.out.println("No");
}
}
}