二分图相关算法 - 匈牙利算法 学习笔记

二分图的概念:如果一个无向图能够被划分为两个集合,使得每一条边的两个端点都在不同的集合中。

下图就是一个二分图。

 

 二分图的一个常见问题就是二分图匹配。匹配是指二分图中两两没有公共点的边集,最大匹配问题则是要求一个所含有的边最多的匹配。


POJ 1274 The Perfect Stall

题意简述:每只牛只会去它喜爱的牛棚产奶,一座牛棚只能容纳一只牛,求最多能有多少牛同时产奶。

很显然这就是一个求二分图最大匹配的问题,解决这类问题的算法之一就是匈牙利算法。我们还是以上图为例演示匈牙利算法。

我们让左侧的节点表示牛,右侧的节点表示牛棚。

从 1 号开始,它可以去 a 牛棚,于是将其分配到a牛棚。

途中橙色的边表示匹配边,即在我们当前求出的匹配中的边;蓝色的线表示非匹配边。

2 号也可以去 a 牛棚,但是 a 牛棚已经有 1 号牛了,为了让 2 号牛去 a 牛棚,我们需要让 1 号牛去别的地方。我们发现可以让 1 号改去 b 牛棚,2 号去 a 牛棚,这样我们就让两只牛都入住牛棚了。

3 号也可以去 a 牛棚,但 a 牛棚现在又有 2 号牛了,同样我们需要让 2 号牛腾出地方。2 号牛还可以去 b 牛棚,递归查询 1 号牛,因为 1 号牛除了 a 和 b 牛棚已无处可去,因此让 2 号牛去 b 牛棚不可行。2 号牛还可以去 c 牛棚,于是让 2 号牛去 c 牛棚,3 号牛去 a 牛棚。

4 号可以去 c 牛棚,但 c 牛棚已经有 2 号牛了。查询 2 号牛,它还可以去 a 牛棚,但 a 牛棚又有 3 号牛;于是递归查询 3 号牛,它还可以去 d 牛棚,d 牛棚是空的。于是让 3 号牛去 d 牛棚,2 号牛去 a 牛棚, 4 号牛去 c 牛棚,皆大欢喜。

由此我们知道,这种算法的执行过程就是“腾”,尽可能让当前的牛可以去的牛棚能够腾空。

AC代码:

#include <cstdio>
#include <algorithm>
#include <cstring>
#include <vector>
using namespace std;
int barn[1010];            //这一数组表示第i个牛棚中装了哪只牛 
bool vis[1010];            //vis数组加速
int n,m;
vector<int> cowwill[1010];
bool dfs(int cown){
    for(auto i:cowwill[cown]){
        if(vis[i]) continue;                //若已被查询过,那么一定是不能腾出的
        vis[i]=true;                        //否则应该已经回溯退出 
        if(!barn[i]||dfs(barn[i])){            //如果这一牛棚为空,或者该牛棚可以腾出空间 
            barn[i]=cown;                    //则将当前牛放入新的牛棚 
            return true;
        }
    }
    return false;
}
int main(){
    int k,t,ans;
    while(scanf("%d%d",&n,&m)==2){
        memset(barn,0,sizeof(barn));
        for(int i=1;i<=n;i++) cowwill[i].clear();    //多测记得清零 
        ans=0;
        for(int i=1;i<=n;i++){
            scanf("%d",&k);
            for(int j=0;j<k;j++){
                scanf("%d",&t);
                cowwill[i].push_back(t); 
            }
        }
        for(int i=1;i<=n;i++){
            memset(vis,0,sizeof(vis));
            if(dfs(i)){
                ++ans;        //如果这一只牛可以放进新的牛棚,则答案+1 
            }
        }
        printf("%d\n",ans);
    }
    return 0;
}

在这里为了简洁,我们将没有与任何一条匹配边直接相连的点叫做未匹配点,其他的叫做匹配点。

注意我们最后一步访问的顺序,4(未匹配点) --非匹配边--  c --匹配边-- 2 --非匹配边-- a --匹配边-- 3 --非匹配边-- d(未匹配点),像这样的,从未匹配点开始,依次途经非匹配边、匹配边、非匹配边、匹配边、... 、非匹配边,最终到达另一个未匹配点的一条路径叫做增广路。

在完成最后一步后,这条路径则变成了 4 --匹配边--  c --非匹配边-- 2 --匹配边-- a --非匹配边-- 3 --匹配边-- d。从这可以引出一个结论:

在二分图中如果存在一条增广路,那么我们向这个匹配中添加这条路径上的所有非匹配边,删除这条路径上的所有匹配边,得到的新边集仍然时一个匹配,并且其中的边比原匹配多一条。

匈牙利算法的本质就是在找增广路,不断改进匹配。可以证明以下结论:

1. 如果二分图中不存在增广路,则当前匹配为最大匹配。

2. 在执行匈牙利算法的过程中,如果现在不能找到从某点开始的增广路,那么以后也找不到。

这两条结论保证了匈牙利算法的正确性。


匈牙利算法的时间复杂度应该是 O(mn).

猜你喜欢

转载自www.cnblogs.com/whatss7/p/12803364.html
今日推荐