欢迎大家访问我的博客:博客传送门
染色法判定二分图
题目描述
二分图
二分图的含义:二分图是有两组点集 X X X和 Y Y Y组成的无向图,其中点集 X X X中的点之间互相没有关联,点集 Y Y Y中的点之间互相没有关联,但是点集 X X X中的点和点集 Y Y Y中的点之间可能存在关联,用一条无向边来表示这种关联。
如下图所示:
橙色节点都属于点集 X X X,互相之间没有边相连;蓝色节点都属于点集 Y Y Y,互相之间没有边相连。绿色代表无向边,用来连接点集 X X X和点集 Y Y Y中的节点。
二分图的判定
圈的定义
图论中圈的定义是:任选一个顶点为起点,沿着不重复的边,经过不重复的顶点,之后又回到起点的闭合路径。
如下图所示:
1 − > 2 − > 4 − > 3 − > 1 1->2->4->3->1 1−>2−>4−>3−>1就是一个圈
- 偶数圈就是经过路径中的顶点数为偶数的圈,如上图, 1 − > 2 − > 4 − > 3 − > 1 1->2->4->3->1 1−>2−>4−>3−>1
- 奇数圈就是经过路径中的顶点数为奇数的圈,如下图, 1 − > 2 − > 4 − > 5 − > 3 − > 1 1->2->4->5->3->1 1−>2−>4−>5−>3−>1
二分图的判定性质
判定性质:判断一个图是不是二分图,其实就是判断这个图有没有奇圈
如下图所示:
我们把这个图划分成了两个点集 X X X和 Y Y Y
但是如果存在奇圈的话,就会变成如下图:
因此,我们可以通过观察奇偶性可知,相邻的两个节点的奇偶性一定是不相同的,分别对应到两个不同的点集 X X X和 Y Y Y中。那么当存在奇圈时,则说明相邻的这两个节点奇偶性一定是相同的,这与二分图的性质矛盾。
染色法判定二分图
二分图染色算法的实现是通过判断一个图中是否存在奇圈从而确定它是否是二分图。
染色法的算法步骤如下:
-
设染色只有2种颜色, c o l o r color color为1和2
-
选择当前还没有被染色的节点作为当前节点 u u u,给它染上颜色1,即 c o l o r [ u ] = 1 color[u]=1 color[u]=1
-
遍历该节点的所有邻接点 v v v,并给这些邻接点染色,即 c o l o r [ v ] = 3 − c o l o r [ u ] color[v]=3-color[u] color[v]=3−color[u]
-
重复上面两个步骤,直到全部染色完毕
-
如果染色过程种发生了矛盾,即相邻两个节点的颜色相同,则说明不满足二分图的奇偶性,因此可以判定出该图不是二分图;否则说明全部都成功无矛盾染色,该图是二分图
注意:由于二分图可能是一个非连通图,所以不是只访问一个结点就能遍历到所有结点的,需要对所有结点都进行一次遍历。
染色法可以用dfs和bfs实现,算法思路其实都是一样的
问题:如何理解dfs染色法中的下列代码呢?
else if(color[j]==c) return false;
如上图,假设当前节点是 u u u,设 u = 4 u=4 u=4,设当前节点 u u u的被染上的颜色为 c c c。我们来看它的邻接点 j = 2 j=2 j=2。我们发现,其实在前面的时候,节点 1 1 1就已经把节点 2 2 2染过色了,染的颜色为 c o l o r [ j ] color[j] color[j]。那么当节点 u u u遍历到节点 j j j时,发现节点 j j j已经被染过色了,那么我们就只需要判断节点 j j j的颜色是否与当前节点 u u u的颜色相同。如果相同,则不符合二分图的奇偶性,则该图不是二分图;
代码
dfs染色法:
//dfs染色法
#include<iostream>
#include<cstring>
using namespace std;
const int N = 100010, M = 200010;
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++;
}
//dfs函数是用来判定染色过程中是否出现了矛盾,如果出现了矛盾,则返回false
//如果没有出现矛盾,就返回true
//这里假设染了两种颜色1 和 2
bool dfs(int u, int c)
{
color[u] = c;//u这个顶点染上c的颜色
//拓展u这个顶点的邻接表,去寻找邻接点
for (int i = h[u]; i != -1; i = ne[i])
{
int j = e[i];
//如果j顶点还没有被染过色
if (!color[j])
{
//如果当这个j顶点染上色之后,就出现了矛盾,那么让flag为false
if (!dfs(j, 3 - c))//3 - c表示所染的颜色一定是在1和2之间
return false;
}
//这里说明j之前已经被染过色了 那么判断一下当前节点u的颜色和它的邻接点j的颜色是否相同
//如果相同则说明一条边的两个顶点都是染相同的颜色,则不满足二分图的奇偶性
//那么就会出现染色矛盾
else if (color[j] == c)
return false;
}
//染色成功
return true;
}
int main()
{
cin >> n >> m;
memset(h, -1, sizeof h);
while (m--)
{
int a, b;
cin >> a >> b;
add(a, b);
add(b, a);
}
bool flag = true;//flag用来判断染色是否有矛盾,如果出现矛盾,则为false,否则就是true
//对1号顶点到n号顶点分别染色
for (int i = 1; i <= n; i++)
{
//如果当前i这个顶点还没有被染色的话 !color[i]表示当前i顶点还没有被染色
if (!color[i])
{
//如果当这个i顶点染上色之后,就出现了矛盾,那么让flag为false,然后break退出for循环
if (!dfs(i, 1))//有两种颜色 颜色1和颜色2
{
flag = false;
break;
}
}
}
//如果染色过程中没有出现矛盾,成功给每个顶点染色了,输出Yes
if (flag)
puts("Yes");
//否则如果染色过程中出现了矛盾,则输出No
else
puts("No");
return 0;
}
bfs染色法
//bfs染色法
#include<iostream>
#include<cstring>
#include<queue>
using namespace std;
const int N = 100010, M = 200010;
int n, m;
int h[N], e[M], ne[M], idx;
int color[N];//给图的每个顶点染色的数组
//bfs的队列
queue<int>q;
//构造无向图
void add(int a, int b)
{
e[idx] = b;
ne[idx] = h[a];
h[a] = idx++;
}
//bfs染色法 c是颜色 0 表示还没有染色 1表示染了一种颜色 2表示染了另一种颜色
bool bfs(int u,int c)
{
color[u]=c; //给u这个节点染上颜色c
q.push(u); //将u这个节点加入队列q中
//当队列不为空时
while(q.size())
{
//取出队头元素
int t=q.front();
q.pop(); //队头元素已经出队了,弹掉队头元素
//遍历队头元素t的所有邻接点
for(int i=h[t];~i;i=ne[i])
{
//队头元素t的邻接点j
int j=e[i];
//如果j还没有被染色
if(!color[j])
{
//由于j是t的邻接点 所以它俩奇偶性不同 染色不一样
color[j]=3-color[t];
//将j加入队列q中
q.push(j);
}
//否则说明j已经被染过色了 但是它的颜色与邻接点t的颜色相同 则不满足奇偶性
else if(color[j]==color[t])
return false;
}
}
return true;
}
int main()
{
memset(h,-1,sizeof h);
scanf("%d%d",&n,&m);
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(!bfs(i,1))
{
flag=false;
break;
}
}
}
if(flag)
puts("Yes");
else
puts("No");
return 0;
}