二部图和匈牙利算法

本文介绍了二部图的概念,通过深搜判断二部图,并详细讲解了匈牙利算法,包括其用于解决最大匹配问题的核心思想——寻找增广路径。通过一个相亲节目的例子直观解释了算法过程,最后提供了相关的实现代码和题例,展示了匈牙利算法在实际问题中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

二部图

二部图的定义:设 G G G是一个图,若 V ( G ) V(G) V(G)有一个划分: V ( G ) = V 1 ⋃ V 2 V(G)=V_1 \bigcup V_2 V(G)=V1V2,使得< V 1 V_1 V1>< V 2 V_2 V2>都是空图,那么称图 G G G是一个双图,或二部图,或二分图。
简单来说,就是在图中切上一刀,就能使所有点变成孤立的,如下图沿着红线切一刀。在这里插入图片描述
判定二分图一般使用标号法,即将一个顶点和与之相邻的顶点标为不同,若此过程可以一直进行下去,则为二分图。
标号法
代码判定
下面给出深搜的判定,广搜当然也可以(只要维护一个队列)。

#include <iostream>
#include <algorithm>
#include <string.h>
#include <stdio.h>
#include <math.h>
using namespace std;
const int N = 505;
int m,n;
int color[N];
int edge[N][N];
 
bool dfs(int v, int c){
    color[v] = c;    //将当前顶点涂色
    for(int i = 0; i < n; i++){    //遍历所有相邻顶点,即连着的点
        if(edge[v][i] == 1){    //如果顶点存在
            if(color[i] == c)    //如果颜色重复,就返回false
                return false;
            if(color[i] == 0 && !dfs(i,-c))    //如果还未涂色,就染上相反的颜色-c,并dfs这个顶点,进入下一层
                return false;   //返回false
        }
    }
    return true;   //如果所有顶点涂完色,并且没有出现同色的相邻顶点,就返回true
}
 
void solve(){
    for(int i = 0; i < n; i++){//对每一点都要判定,因为图可能不是连通的
        if(color[i] == 0){
            if(!dfs(i, 1)){
                printf("NOT BICOLORABLE.\n");
                return;
            }
        }
    }
    printf("BICOLORABLE.\n");
}
 
int main(){
    int u,v;
    while(cin >> n >> m){
        memset(color, 0, sizeof(color));
        memset(edge, 0, sizeof(edge));
        for(int i = 0; i < m; i++){
            cin >> u >> v;    //因为是无向图,所以要往两个方向添加边
            edge[u][v] = 1;    //正着添加
            edge[v][u] = 1;    //反着添加
        }
        solve();
    }
    return 0;
}

匈牙利算法

基本概念

  • G G G的一个匹配是由一组没有公共端点不是圈构成的集合。
    如图就是之前的图的一个匹配。可知:1. 匹配是边的集合;2. 在该集合中,任意两条边不能有共同的顶点。
    在这里插入图片描述
    就好像参加相亲节目,在不考虑同性恋的情况下,要给匹配合适的对象,有边代表男女嘉宾互有好感。所谓最大匹配,就是节目效果要达到最好,需要尽可能多的互有好感的人配对。
    匈牙利算法可以解决二部图上最大匹配问题。该算法的核心就是寻找增广路径,它是一种用增广路径求二分图最大匹配的算法。
    ——上面的描述看起来很绕而且有很多概念对不对,下面我们直观地感受一下算法的过程。你会发现,其实我们做的是月老的活。
    首先,我们这个相亲节目在先来后到的基础上很照顾后来的人,会优先考虑后来人的需求,但同时也不能拆散先来的人让他遗憾离场。
    在这里插入图片描述
    现在有三个男嘉宾和三个女嘉宾,男嘉宾有选择权。先从一号男嘉宾开始,他选择了1号女嘉宾,3号女嘉宾成为了他的备胎。
    在这里插入图片描述
    2号男嘉宾来了,他说我也要1号女嘉宾,1号男嘉宾你往后稍稍。节目组这时候劝说1号男嘉宾,哎,你还有3号备胎,你把1号女嘉宾让给2号男嘉宾吧!
    1号一想也是,不如顺便送个人情,于是他换成了3号女嘉宾,2号男嘉宾如愿匹配了1号女嘉宾,并把2号女嘉宾作为了自己的备胎。
    在这里插入图片描述
    这时3号男嘉宾上场,他说我为1号而来!我只要1号!节目组的老好人们又去劝说正和1号牵线的2号男嘉宾。当然2号男嘉宾也同意了,毕竟他还有2号备胎嘛!
    于是本期节目皆大欢喜,大家都牵手成功~
    在这里插入图片描述
    ——这个匹配即是最大匹配,也是完美匹配。
    但是有没有失败而归的情况呢?也是有的:
    假如是这样一张图:
    在这里插入图片描述
    1号2号男嘉宾已经进行了协调和选择:
    在这里插入图片描述
    这时候3号男嘉宾来了,还是非1号不可。节目组劝说2号,2号说我要看看备胎在不在,于是询问3号女嘉宾。
    3号女嘉宾说我得问问1号男嘉宾还有没有备胎。(这是一个递归的过程
    1号男嘉宾:你就是我的备胎!我的原配已经给2号了!
    于是3号女嘉宾告诉2号,对不起啊,1号没有选择了,我也不当你的备胎了。
    于是2号男嘉宾告诉节目组,对不起啊,我也没有选择了,我不让。
    节目组当然尊重他们的选择,毕竟不能强行拆散,只为了3号男嘉宾。所以节目组告诉3号男嘉宾,对不起啊,你没有牵手成功。
    匹配结果如下:
    在这里插入图片描述
    这是一个最大匹配(满足多数人的需求),但不是完美匹配。

实现代码

题例:POJ1274
大意:有n个槽和m头牛,牛只在特定的几个槽才产奶,求最多几头牛可以产奶。

#include <stdio.h>
#include <iostream>
#include <string.h>
using namespace std;
const int maxn = 220;
int n,m;
int mark[maxn];
int match[maxn];
int maze[maxn][maxn];
int ans;

bool Find(int x){
    for(int i = 1;i <= m;++i){
        if(maze[x][i] && !mark[i]){  //如果牛在这里产奶且槽还没问过
            mark[i] = 1;
            if(match[i] == 0 || Find(match[i])){//如果没分配或者分配了但是可以找到别的给那个对象
                match[i] = x;
                return true;
            }
        }
    }
    return false;
}

void slove(){
    memset(match,0,sizeof(match));
    ans = 0;
    for(int i = 1; i <= n;++i){
        memset(mark,0,sizeof(mark));
        if(Find(i))ans++;
    }
}

int main()
{
    while(~scanf("%d%d",&n,&m)){
        for(int i = 1; i <= n;++i)
            for(int j = 1; j <= m;++j)
            maze[i][j] = 0;

        for(int i = 1; i <= n;++i){
            int x;
            scanf("%d",&x);
            for(int j = 1; j <= x;++j){
                    int tmp;
                scanf("%d",&tmp);
                maze[i][tmp] = 1;
            }
        }
        slove();
        printf("%d\n",ans);
    }
    return 0;
}

最大匹配数=最小点覆盖数。因此最小点覆盖也可以转化为最大匹配问题。
一些例题:
POJ1325
POJ3692(需要建补图,是最大独立集的问题)
POJ1469
POJ3041*这个问题的转化比较巧妙了,题目大意是一个网格里有一些障碍物,一发炮弹可以清除同一行或者同一列的障碍物,问最少开几枪。转化障碍物坐标,行坐标为一个集合,列坐标为一个集合,障碍物的坐标连线,使用匈牙利算法。
(大概是考虑在同一行则行坐标相等,尽可能多的为不同的行坐标都能匹配到列坐标。。。)
应用可以参考这篇博客:匈牙利算法—介绍与基本用途

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值