AcWing
  • 首页
  • 题库
  • 题解
  • 分享
  • 问答
  • 活动
  • 应用
  • 吐槽
  • 登录/注册

图论 -- 二分图

作者: 作者的头像   咲张熊猫人 ,  2021-02-18 23:14:06 ,  阅读 32


1


2

二分图

  |------------染色法 O(n2)
  |                          
  | 
  | 
  |
  二分图相关算法    
  |
  |
  |
  |
  |----------- 匈牙利算法 (最坏O(mn), 但实际运行效果较好,远小于mn)

首先搞清楚二分图的概念,简而言之,就是顶点集V可分割为两个互不相交的子集,并且图中每条边依附的两个顶点都分属于这两个互不相交的子集,两个子集内的顶点不相邻。
5.png

二分图中的一个重要性质: 若一个图中存在奇数环(即存在节点数为奇数个的环),则该图不可能是二分图。 因为根据二分图的定义,若要保证两个集合的中间不存在节点,故每一条边的两个端点必定是不同的部分,若存在奇数环,则从任意一个点开始,标记为1,表示左半部分,然后其连接的下一节点必定为右部分,标记为2,依次类推再下一个标记为1,若为奇数环,则就会出现矛盾的情况。

染色法

其实就是上面分析性质的过程,遍历所有的节点,然后遍历其连接的下一节点,依次标记为不同的颜色,这里的话在算法实现上dfs和bfs都是可以的,若在这个过程中出现矛盾的情况则表示该图不为二分图.

练习链接: 860. 染色法判定二分图
代码实现

#include<iostream>
#include<cstring>
using namespace std;

constexpr int N = 2e+5 + 10;

int n,m,g[N], e[N], ne[N], idx; // 为了方便遍历一个节点所相连的其他节点,这里采用邻接表储存
int color[N];

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


auto dfs(int u, int c) -> bool    // 这里采用dfs遍历每个节点及其所相连的节点
{
    color[u] = c;
    for(int i = g[u]; i != -1; i = ne[i])   // 依次遍历u节点所相连的其他节点    
    {
        int t = e[i]; 
        if(!color[t])             // 若该节点为被染色, 则进行染色, 1, 2代表两种颜色
        {
            if(!dfs(t, 3 - c)) return false;   // 3 - c 表示染上和c不同的颜色
        }
        else if(color[t] == c) return false;   // 若该节点未被染色并且和上一节点颜色相同则矛盾
    }
    return true;
}

auto main() -> int
{
    memset(g, -1, sizeof g);
    cin >> n >> m;
    while(m--)
    {
        int a, b; cin >> a >> b;
        add(a, b), add(b,a);
    }
    for(int i = 1; i <= n; ++i)   // 依次遍历每一个节点
    {
        if(!color[i])
        {
            if(!dfs(i, 1))        // 若出现冒充直接goto到end结束
            { 
                cout << "No" << endl;
                goto end;
            }
        }
    }
    cout << "Yes" << endl; 
end:
    return 0;
}

匈牙利算法

匈牙利算法主要解决二分图中的最大匹配问题,首先来了解一下什么叫二分图的匹配, 给定一个二分图G,在G的一个子图M中,M的边集{E}中的任意两条边都不依附于同一个顶点,则称M是一个匹配。
6.png
如左图所示的是一个二分图,可以分为左部分集合为 u, 右半部分集合为v, 然后根据匹配的定义,任意两条边都不依附于同一个顶点。我们可以看到左图的5号顶点中,有三条边依附于5号节点上, 2条边依附于7号节点上。 故这些边并不能算作是一种匹配的情况,故在右图的情况中属于图的一个边集, 即符合匹配的条件,且最大匹配数即为为4条边,故结果为4.

故匈牙利算法的思路: 模拟一下算法的过程, 首先从原本的二分图中,我们看作是遍历作半部分的节点,然后找到唯一一个与其匹配的右边节点,例如从左图中1号节点可以匹配5号节点和7号节点,故先让其匹配5号节点,此时算作一个匹配,然后遍历左边的下一节点2,此时它只能与5号点匹配,但是5号节点已经和1号点匹配,此时再考虑1号点有没有其他匹配的方案,发现1号点可以和7号匹配,故1号重新和7号匹配,2号和5号匹配, 故此时已经有两个匹配,然后继续遍历左边剩余节点,此时遍历3号节点,3号节与5号节点和6号节点相连,我们从上到下的顺序依次判断,首先是5号节点,此时5号节点已经和2号节点匹配,然后回去判断2号节点是否能和其他节点匹配,发现没有其他节点符合条件,故此时再判断6号节点,发现还没有其他节点与其匹配,故匹配成功。继续遍历左边的剩余节点。此时剩下4号节点,与其相连的节点有7号节点和8号节点,首先判断是否能和7号节点匹配,发现7号节点已经和1号节点匹配,故看看1号节点能不能和其他节点匹配,发现其能和5号节点匹配,但5号节点已经和2号节点匹配,故再看看2号节点能不能换一个匹配,发现不行。故4号点此时不能和7号点匹配,故判断一下能否和8号点匹配,发现8号点还没有匹配,故成功匹配上。 故左边所有节点匹配完毕,匹配数为4;

分析完过程,看看代码实现
练习链接 : 861. 二分图的最大匹配

#include<iostream>
#include<cstring>
using namespace std;
constexpr int N = 1e+5 + 10;
int n1, n2, m, e[N], ne[N], idx, g[N];// 为了便于找出节点相连的节点,采用邻接表的方式更方便
int match[N];// 用于标记右半部分的节点匹配的是左半部分那个节点    
bool st[N];  // 用于表示那个节点已经处于尝试匹配当中,或者就是表示该节点准备被匹配,其他已匹配到的节点另寻其他节点

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

auto find(int x) -> bool    // 寻找未被匹配的节点
{
    for(int i = g[x]; i != -1; i = ne[i])  // 依次遍历与自身节点相连的其他节点
    {
        int t = e[i];               
        if(st[t]) continue;    // 若该节点已被其他节点尝试匹配,则尝试下一节点
        st[t] = true;          // 标记右边的t节点为尝试匹配状态
        if(!match[t] || find(match[t]))  // 若此时右边的t节点未被匹配, 或者左边匹配到t的节点能换成另外一个节点则匹配成功
        {
            match[t] = x;      // 更新右边的t节点匹配左边的x节点
            return true;
        }
    }
    return false;
}

auto main() -> int
{
    cin >> n1 >> n2 >> m;
    memset(g, -1, sizeof g);
    while(m--)
    {
        int a, b; cin >> a >> b;
        add(a, b);
    }
    int res = 0;
    for(int i = 1; i <= n1; ++i)
    {
        memset(st, false, sizeof st);
        if(find(i)) res++;      // 匹配成功依次,则匹配数++
    } 
    cout << res << endl;

}

0 评论

你确定删除吗?

© 2018-2021 AcWing 版权所有  |  京ICP备17053197号-1
联系我们  |  常见问题
AcWing
请输入登录信息
更多登录方式: 微信图标 qq图标
请输入绑定的邮箱地址
请输入注册信息