欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

匈牙利算法【匹配问题】

程序员文章站 2022-04-16 12:54:53
...

匈牙利算法:

匈牙利算法几乎是二分图匹配的核心算法,除了二分图多重匹配外均可使用

匈牙利算法实际上就是一种网络流的思想,其核心就是寻找增广路。具体操作就是嗯。。拉郎配

 

匈牙利算法适用的场合:

  1. 二分图匹配
  2. 链上的匹配问题
  3. 一般图匹配等其他点的个数较小的匹配问题

 

举个以前写的博客的例子

 

通过数代人的努力,你终于赶上了剩男剩女的大潮,假设你是一位光荣的新世纪媒人,在你的手上有N个剩男,M个剩女,每个人都可能对多名异性有好感(-_-||暂时不考虑特殊的性取向),如果一对男女互有好感,那么你就可以把这一对撮合在一起,现在让我们无视掉所有的单相思(好忧伤的感觉),你拥有的大概就是下面这样一张关系图,每一条连线都表示互有好感。

匈牙利算法【匹配问题】

 

本着救人一命,胜造七级浮屠的原则,你想要尽可能地撮合更多的情侣,匈牙利算法的工作模式会教你这样做:

===============================================================================

一: 先试着给1号男生找妹子,发现第一个和他相连的1号女生还名花无主,got it,连上一条蓝线

匈牙利算法【匹配问题】

 

===============================================================================

:接着给2号男生找妹子,发现第一个和他相连的2号女生名花无主,got it

匈牙利算法【匹配问题】

 

===============================================================================

:接下来是3号男生,很遗憾1号女生已经有主了,怎么办呢?

我们试着给之前1号女生匹配的男生(也就是1号男生)另外分配一个妹子。

 

(黄色表示这条边被临时拆掉)

匈牙利算法【匹配问题】

 

与1号男生相连的第二个女生是2号女生,但是2号女生也有主了,怎么办呢?我们再试着给2号女生的原配重新找个妹子(注意这个步骤和上面是一样的,这是一个递归的过程)

 

匈牙利算法【匹配问题】

 

此时发现2号男生还能找到3号女生,那么之前的问题迎刃而解了,回溯回去

 

2号男生可以找3号妹子~~~                  1号男生可以找2号妹子了~~~                3号男生可以找1号妹子

匈牙利算法【匹配问题】匈牙利算法【匹配问题】匈牙利算法【匹配问题】

所以第三步最后的结果就是:

匈牙利算法【匹配问题】

 

===============================================================================

: 接下来是4号男生,很遗憾,按照第三步的节奏我们没法给4号男生腾出来一个妹子,我们实在是无能为力了……香吉士同学走好。

 

===============================================================================

这就是匈牙利算法的流程,其中找妹子是个递归的过程,最最关键的字就是“腾”字

其原则大概是:有机会上,没机会创造机会也要上

 

算法步骤(基于二分图)

  1. 如果题目初始的时候给出的是一副一般图,我们可以利用拓扑染色(2-SAT、种类并查集等等)的方式来让他变成一个二分图。
  2. 将二分图分成X部和Y部,我们现在的做法是通过X部去匹配Y部;
  3. 我们从1~cnt_X的去枚举,如果没有被对面的Y给匹配过的,我们试着让他去匹配对应的有链接边的Y部,如果对面的Y部对应的点y是没有被匹配的,或者链接对应y的x是可以换一个没有被匹配过的点的,那么我们就让x去换,并且让我们现在枚举的1~cnt_X去链接对应的点
  4. 在算法上的优化,我们不难发现,如果目前的1~cnt_Y中的某个y在这一回合的匹配中是被利用过了的,那么也就是说,后面的点都是已经被操作过(且为false的),说明我们不用再继续往下跑了,所以可以利用一个vis标记来进行优化。在枚举x的1~cnt_X的同时,我们进入“腾”这个操作的时候,就是需要去对1~cnt_Y进行vis清空操作。

 

算法步骤(基于链)

  1. 链上的匈牙利算法的算法结构并不特殊,只是对于建边的时候需要慎重考虑。
  2. 它是在一条链上的匹配,我们当去寻求匹配对象的时候,我们要尽可能的先把前面的link[x]覆盖,不然当我们匹配到后面的时候却已经被覆盖了,不是最优解。
  3. 所以,我们的建边尽可能是要从后面的结点指向前面的结点,例如i -> j(i > j)
  4. 然后,我们1~N的枚举,当前的点一定是没有被匹配过的,因为操作3保证了。所以,我们直接去寻求匹配即可。

 

例题讲解(不按难度顺序排列)

 


Oil Skimming

 HDU - 4185

  这道题的精髓在于,i点和j点是成对匹配的。也就是说,当我们匹配上j点的时候,事实上,i点同时被j点匹配,所以我们一次操作的时候是需要赋值两次。

            link[v] = u;
            link[u] = v;

也就是这样。

My Code


Taxi Cab Scheme

 HDU - 1350

  这道题就是一道很模版的匈牙利算法在链上的操作了。

  有N个人,他们在特定的时间(hour : minute)要从地点(a, b)前往地点(c, d),然后呢,我们如果说前一个人i的出租车可以开回来再接j这个人,那么他们就可以少叫一辆出租车了。现在,题目想知道最少的需求的出租车的个数。

图文讲解 + 代码


过山车

 HDU - 2063

  匈牙利算法的模板题,这里给出主要的结构。理清楚,我们在哪个位置要清空X的内容,在哪个位置是要去清空Y部的内容,我这里并没有去用拆点或者使用memset()来逃避这两个问题。

bool match(int u)
{
    for(int i=head[u], v; ~i; i=edge[i].nex)
    {
        v = edge[i].to;
        if(vis[v]) continue;
        vis[v] = true;
        if(!link[v] || match(link[v]))
        {
            link[v] = u;
            return true;
        }
    }
    return false;
}
inline int Hungery()
{
    int ans = 0;
    for(int i=1; i<=M; i++) link[i] = 0;
    for(int i=1; i<=N; i++)
    {
        for(int j=1; j<=M; j++) vis[j] = false;
        if(match(i)) ans++;
    }
    return ans;
}

这两行是主要的代码,要仔细看清哪里用的是X,哪里用的是Y。(N对应X,M对应Y)


棋盘游戏

 HDU - 1281

  看着题意很复杂,但是我们可以简化一下。

  不能横竖给交叉了,实际上指的是每个对应的x坐标对应唯一的y坐标,我们基于这点去对于二维平面进行匈牙利算法即可。复杂度是匈牙利算法【匹配问题】,其中的匈牙利算法【匹配问题】源于匈牙利算法的复杂度。

  判断是否是重要结点,可以自己想想看。

#include <iostream>
#include <cstdio>
#include <cmath>
#include <string>
#include <cstring>
#include <algorithm>
#include <limits>
#include <vector>
#include <stack>
#include <queue>
#include <set>
#include <map>
#include <bitset>
#include <unordered_map>
#include <unordered_set>
#define lowbit(x) ( x&(-x) )
#define pi 3.141592653589793
#define e 2.718281828459045
#define INF 0x3f3f3f3f
#define HalF (l + r)>>1
#define lsn rt<<1
#define rsn rt<<1|1
#define Lson lsn, l, mid
#define Rson rsn, mid+1, r
#define QL Lson, ql, qr
#define QR Rson, ql, qr
#define myself rt, l, r
#define MP(a, b) make_pair(a, b)
using namespace std;
typedef unsigned long long ull;
typedef unsigned int uit;
typedef long long ll;
const int maxN = 105;
int N, M, K, head[maxN], cnt, cn_X, cn_Y;
vector<pair<int, int>> vt;
struct Eddge
{
    int nex, to;
    Eddge(int a=-1, int b=0):nex(a), to(b) {}
}edge[maxN * maxN];
inline void addEddge(int u, int v)
{
    edge[cnt] = Eddge(head[u], v);
    head[u] = cnt++;
}
int link[maxN];
bool vis[maxN];
inline bool match(int u)
{
    for(int i=head[u], v; ~i; i=edge[i].nex)
    {
        v = edge[i].to;
        if(u == cn_X && v == cn_Y) continue;
        if(vis[v]) continue;
        vis[v] = true;
        if(!link[v] || match(link[v]))
        {
            link[v] = u;
            return true;
        }
    }
    return false;
}
inline int Hungery()
{
    int ans = 0;
    for(int i=1; i<=M; i++) link[i] = 0;
    for(int i=1; i<=N; i++)
    {
        for(int j=1; j<=M; j++) vis[j] = false;
        if(match(i)) ans++;
    }
    return ans;
}
inline void init()
{
    cnt = 0; cn_X = cn_Y = 0; vt.clear();
    for(int i=1; i<=N; i++) head[i] = -1;
}
int main()
{
    int Cas = 0;
    while(scanf("%d%d%d", &N, &M, &K) != EOF)
    {
        init();
        for(int i=1, u, v; i<=K; i++)
        {
            scanf("%d%d", &u, &v);
            addEddge(u, v);
            vt.push_back(MP(u, v));
        }
        int const_ans = Hungery(), Important = 0;
        for(int i=0; i<K; i++)
        {
            cn_X = vt[i].first; cn_Y = vt[i].second;
            if(Hungery() < const_ans) Important++;
        }
        printf("Board %d have %d important blanks for %d chessmen.\n", ++Cas, Important, const_ans);
    }
    return 0;
}
相关标签: 匈牙利算法