1. 程式人生 > >二分圖匹配的匈牙利算法

二分圖匹配的匈牙利算法

就是 ref inline 算法 return sca log 數學家 method

好久之前就看了匈牙利,現在都快忘光了,於是寫寫\(\tt{Blog}\)加深一下印象


匈牙利算法簡介

按照我的慣例,引用一下某度百科

匈牙利算法(Hungarian method)是由匈牙利數學家Edmonds於1965年提出,因而得名。匈牙利算法是基於Hall定理中充分性證明的思想,它是二分圖匹配最常見的算法,該算法的核心就是尋找增廣路徑,它是一種用增廣路徑二分圖最大匹配的算法

求二分圖最大匹配常用的有兩種算法,一種是網絡流,一種是匈牙利。時間復雜度上來講,網絡流要比匈牙利快的多。但是從代碼復雜度上來講,匈牙利要比網絡流好寫的多。

匈牙利時間復雜度:鄰接矩陣為\(O(n^3)\),鄰接表為\(O(nm)\)

算法思想

上面已經說過了,匈牙利是用增廣路徑來求二分圖最大匹配。看著很高大上,但是當你理解後,你會發現匈牙利其實十分暴力

讓我們模擬一下
技術分享圖片
對於上面這個圖,我們先讓\(0->6\),沒有問題

技術分享圖片
接下來讓\(1->7\),並沒有發生沖突,也沒有問題

技術分享圖片
接下來讓\(2->6\),但是6已經和1匹配了,但2並不管這些,後來者居上,於是1就被NTR了 2和1匹配,而0就只能去找它的二號對象\(->7\)

技術分享圖片
接下來3、4都比較平和,沒有強制讓位的情況發生。

再接下來的情況,我就懶得模擬了,諸位客官見諒。

其實匈牙利的算法核心就是NTR別人和不斷被NTR

代碼實現

Luogu P3386【模板】二分圖匹配

匈牙利的代碼超級簡單,這也是它最大的優勢,它運用了遞歸去不斷NTR別人尋找增廣路。我是用鄰接表實現的

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int n,m,k;
struct zzz{
    int t,
        nex;
}e[1001*1001*4];
int head[4001],tot;
void add(int x,int y)
{
    e[++tot].t=y;
    e[tot].nex=head[x];
    head[x]=tot;
}
int ans;
bool vis[4001];
int pp[4001]; //pp[i] 用來存i的當前匹配的對象是誰
bool find(int x) //尋找增廣路
{
    for(int i=head[x];i;i=e[i].nex)
    {
        int to=e[i].t;
        if(!vis[to])
        {
            vis[to]=1;
            if(!pp[to]||find(pp[to])) //搶占他人的匹配對象,然後再為被搶的人找一個新的對象
            {
                pp[to]=x;
                return 1;
            }
        }
    }
    return 0;
}
int main()
{
    scanf("%d%d%d",&n,&m,&k);
    for(int i=1;i<=k;i++)
    {
        int x,y; scanf("%d%d",&x,&y);
        if(y>m||x>n) continue;
        add(x,y);
    }
    for(int i=1;i<=n;i++) //為每個節點尋找匹配對象
    {
        memset(vis,0,sizeof(vis));
        if(find(i))
          ans++;
    }
    
    printf("%d",ans);
    
    return 0;
}

Luogu P2756 飛行員配對方案問題
匈牙利+輸出方案

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
inline int read()
{
    int k=0,f=1; char c=getchar();
    for(;!isdigit(c);c=getchar())
      if(c=='-')
        f=-1;
    for(;isdigit(c);c=getchar())
      k=(k<<3)+(k<<1)+c-48;
    return k*f;
}
struct zzz{
    int t,
        nex;
}e[10001*2]; int head[101],tot;
inline void add(int x,int y)
{
    e[++tot].t=y;
    e[tot].nex=head[x];
    head[x]=tot;
}
bool vis[101];int lin[101];
bool find(int x)
{
    for(int i=head[x];i;i=e[i].nex)
    {
        if(!vis[e[i].t])
        {
            vis[e[i].t]=1;
            if(!lin[e[i].t]||find(lin[e[i].t]))
            {
                lin[e[i].t]=x;
                return 1;
            }
        }
    }
    return 0;
}
int ans;
int main()
{
    int n,m; m=read(),n=read();
    while(1)
    {
        int x=read(),y=read();
        if(x==-1&&y==-1)
          break;
        add(x,y); add(y,x);
    }
    for(int i=1;i<=n;i++)
    {
        memset(vis,0,sizeof(vis));
        if(find(i)) ans++;
    }
    if(ans==0)
      cout<<"No Solution!";
    cout<<ans/2<<endl;
    
    //輸出方案
    for(int i=m;i<=n;i++)
    {
        if(lin[i]>0&&lin[i]<=m&&ans)
        {
            ans--;
            printf("%d %d\n",lin[i],i);
        }  
    }
    return 0;
}

二分圖匹配的匈牙利算法