算法学习笔记(31)——染色法判定二分图

染色法判定二分图

如果一张无向图的 \(N\) 个节点(\(N \geq 2\))可以分成 \(A,B\) 两个非空集合,其中 \(A \cap B = \emptyset\),并且在同一集合内的点之间都没有边相连,那么称这张无向图为一张二分图\(A,B\) 分别称为二分图的左部和右部。

定理:
一张无向图是二分图,当且仅当图中不存在奇环(长度为奇数的环)。

根据上述定理,我们可以用染色法进行二分图的判定。染色法的基本思路是:尝试用黑白两种颜色标记图中的节点,当一个节点被标记之后,它的所有相邻节点应该被标记为与它相反的颜色。若标记过程中产生冲突,则说明图中存在奇环。二分图染色一般基于深度优先遍历实现,时间复杂度为 \(O(N+M)\)

题目链接:AcWing 860. 染色法判定二分图

在代码实现中,由于染色过程中需要快速知道某个节点的相邻节点,所以采用邻接表存储图;由于是无向图,所以需要每次存双向边。

color[]数组表示当前节点的颜色,0表示未染色,12是两种颜色,染色时可用3-c表示与c相反的颜色。

#include <iostream>
#include <cstring>

using namespace std;

const int N = 1e5 + 10, M = N * 2;

int n, m;
// 邻接表存储无向图,注意无向图需要对边相关的数组开两倍的内存空间
int h[N], e[M], ne[M], idx;
// 染色数组,0代表未染色,1和2表示两种颜色
int color[N];

void add(int a, int b)
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
}

// 将节点u染为c色,并检查其相邻节点
bool dfs(int u, int c)
{
    // 将u染为c色
    color[u] = c;
    
    // 遍历相邻节点
    for (int i = h[u]; i != -1; i = ne[i]) {
        int j = e[i];
        // 如果当前节点还没有染色
        if (!color[j]) {
            // 将j染为相反的颜色,若有冲突则返回false,染色失败
            if (!dfs(j, 3 - c)) return false;
        }
        // 若当前节点已经染色,则判断相邻节点颜色是否相同,若相同则返回false,染色失败
        else if (color[j] == c) return false;
    }
    
    // 整个连通块都能成功染色,返回true
    return true;
}

int main()
{
    // 初始化邻接表
    memset(h, -1, sizeof h);
    
    cin >> n >> m;
    
    while (m -- ) {
        int u, v;
        cin >> u >> v;
        // 无向图每次添加双向边
        add(u, v), add(v, u);
    }
    
    // flag记录是否为二分图,默认为true
    bool flag = true;
    // 循环处理每一个节点所在的连通块
    for (int i = 1; i <= n; i ++ )
        // 如果当前节点没有染色,说明是一个新的连通块
        if (!color[i]) {
            // 用染色法进行染色,若染色失败,则该图不是二分图,跳出循环
            if (!dfs(i, 1)) {
                flag = false;
                break;
            }
        }
    
    if (flag) puts("Yes");
    else      puts("No");
    
    return 0;
}
posted @ 2022-12-10 09:33  S!no  阅读(170)  评论(0编辑  收藏  举报