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

二分圖的最大匹配 匈牙利演算法

幾種概念:

二分圖:是圖論中的一種特殊模型。若能將無向圖G=(V,E)的頂點V劃分為兩個交集為空的頂點集,並且任意邊的兩個端點都分屬於兩個集合,則稱圖G為一個為二分圖。可以想象一下離散數學中的二部圖。
這裡寫圖片描述

匹配:一個匹配即一個包含若干條邊的集合,且其中任意兩條邊沒有公共端點。如下圖,圖3的紅邊即為圖2的一個匹配。
這裡寫圖片描述
這裡寫圖片描述

1 最大匹配
在G的一個子圖M中,M的邊集中的任意兩條邊都不依附於同一個頂點,則稱M是一個匹配。選擇這樣的邊數最大的子集稱為圖的最大匹配問題,最大匹配的邊數稱為最大匹配數.如果一個匹配中,圖中的每個頂點都和圖中某條邊相關聯,則稱此匹配為完全匹配,也稱作完備匹配

。如果在左右兩邊加上源匯點後,圖G等價於一個網路流,最大匹配問題可以轉為最大流的問題。解決此問的匈牙利演算法的本質就是尋找最大流的增廣路徑。

2 最優匹配

最優匹配又稱為帶權最大匹配,是指在帶有權值邊的二分圖中,求一個匹配使得匹配邊上的權值和最大。一般X和Y集合頂點個數相同,最優匹配也是一個完備匹配,即每個頂點都被匹配。如果個數不相等,可以通過補點加0邊實現轉化。一般使用KM演算法解決該問題。

3 最小覆蓋

二分圖的最小覆蓋分為最小頂點覆蓋和最小路徑覆蓋:

①最小頂點覆蓋是指最少的頂點數使得二分圖G中的每條邊都至少與其中一個點相關聯,二分圖的最小頂點覆蓋數=二分圖的最大匹配數

②最小路徑覆蓋也稱為最小邊覆蓋,是指用盡量少的不相交簡單路徑覆蓋二分圖中的所有頂點。二分圖的最小路徑覆蓋數=|V|-二分圖的最大匹配數

4 最大獨立集

最大獨立集是指尋找一個點集,使得其中任意兩點在圖中無對應邊。對於一般圖來說,最大獨立集是一個NP完全問題,**對於二分圖來說最大獨立集=|V|-二分圖的最大匹配數**。

基本概念—匈牙利演算法

交替路:從一個未匹配點出發,依次經過非匹配邊、匹配邊、非匹配邊…形成的路徑叫交替路。*

增廣路:從一個未匹配點出發,走交替路,如果途徑另一個未匹配點(出發的點不算),則這條交替路稱為增廣路(agumenting path)。

二、最大匹配與最小點覆蓋

最小點覆蓋:假如選了一個點就相當於覆蓋了以它為端點的所有邊,你需要選擇最少的點來覆蓋所有的邊

最小割定理是一個二分圖中很重要的定理:一個二分圖中的最大匹配數等於這個圖中的最小點覆蓋數

最小點集覆蓋==最大匹配。在這裡解釋一下原因,首先,最小點集覆蓋一定>=最大匹配,因為假設最大匹配為n,那麼我們就得到了n條互不相鄰的邊,光覆蓋這些邊就要用到n個點。現在我們來思考為什麼最小點選覆蓋一定<=最大匹配。任何一種n個點的最小點選覆蓋,一定可以轉化成一個n的最大匹配。因為最小點集覆蓋中的每個點都能找到至少一條只有一個端點在點集中的邊(如果找不到則說明該點所有的邊的另外一個端點都被覆蓋,所以該點則沒必要被覆蓋,和它在最小點集覆蓋中相矛盾),只要每個端點都選擇一個這樣的邊,就必然能轉化為一個匹配數與點集覆蓋的點數相等的匹配方案。所以最大匹配至少為最小點集覆蓋數,即最小點選覆蓋一定<=最大匹配。綜上,二者相等。

三、匈牙利演算法

由增廣路的性質,增廣路中的匹配邊總是比未匹配邊多一條,所以如果我們放棄一條增廣路中的匹配邊,選取未匹配邊作為匹配邊,則匹配的數量就會增加。匈牙利演算法就是在不斷尋找增廣路,如果找不到增廣路,就說明達到了最大匹配。
(以上內容引自神犇大佬)

你可以理解為左邊集合全是男生,右邊集合全為女生,他們已經存在著某種聯絡,現在你要將其湊成情侶,問你最多能湊成多少對。

若初始化給你的關係是這樣的:
這裡寫圖片描述
(1)先給男生1號找妹子,發現她與女生1號有些聯絡,而女生一號也沒有心儀物件,則他倆湊對
這裡寫圖片描述

(2)然後在給男生2號找妹子,發現女生二號可以,湊對
這裡寫圖片描述
(3)接下來是男生3號,發現男生3號與女生1號有一腿,則去看看可不可以給男生1號重新找一個妹子,讓男生3號安穩的隔壁老王變男主人。

(黃色代表暫時拆掉這條線)
這裡寫圖片描述
男生1號找到了女生2號,發現她也有中意的人,則他也要隔壁老王變男主人,給女生2號的原配找一個新的情人。(遞迴過程
這裡寫圖片描述

發現男生2號還可以找到女生3號這個小情人 ,則一切問題都迎刃而解。(回溯

男2找女3
這裡寫圖片描述

男1找女2
這裡寫圖片描述

男3找女1
這裡寫圖片描述

第三步的結果:這裡寫圖片描述

(4)給男4找妹子,但是按照第三步的步驟來發現他們實在沒有辦法在空出一個妹子來給男4,所以他只能孤獨終老

這大概就是匈牙利演算法的大體過程,主要的步驟還是在那個dfs身上會這一部分基本就掌握了這個演算法。

程式碼模板:

//匈牙利演算法
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <string>
using namespace std;
const int N=1e6+10;
const int MAXN=800;
const double pai=4*atan(1);
int un,vn;//un 代表男生總數 vn 代表女生總數
int g[MAXN][MAXN];//鄰接矩陣
int linker[MAXN];//女生中意的人
bool used[MAXN];//標記陣列
int temp;


bool dfs(int u)//僅對男生使用dfs 這是該演算法的關鍵點 精髓之處
{
    for(int v=1;v<=vn;v++)
    {
        if(g[u][v]&&!used[v])
        {
            used[v]=true;
            if(linker[v]==-1||dfs(linker[v]))
            {
                linker[v]=u;
                return true;
            }
        }
    }
    return false;
}

int hungary()
{
        int res=0;
        memset(linker,-1,sizeof(linker));
        for(int u=1;u<=un;u++)
        {
            memset(used,false,sizeof(used));
            if(dfs(u))
                res++;
        }
        return res;
}

int main()
{
    ios::sync_with_stdio(false);
    while(cin>>un>>vn)
    {
        int n;
        cin>>n;
        for(int i=1;i<=n;i++)
        {
            int u,v;
            cin>>u>>v;
            g[u][v]=1;

        }
        cout<<hungary()<<endl;
    }
    return 0;
}