之前大一的時候有學姐講過二分圖匹配的匈牙利(Hungrain)算法,當時沒理解。最近想補補圖論,學習一下二分圖匹配的匈牙利算法,其實挺簡單的。
先弄清二分圖匹配時重要的幾個概念:
(1)交替路:從一個未匹配的頂點出發,依次經過未匹配邊,匹配邊...,這樣由匹配邊,非匹配邊交替形成的路稱爲交替路。一定先理解這個概念,才能理解增廣路的概念。
(2)增廣路:從一個未匹配的頂點出發,沿交替路走,到達第一個未匹配頂點時到達終點,這個過程形成的路稱爲增廣路。
增加匹配的關鍵在於在整個圖中尋找增廣路,如果找不到增廣路,則已經找到最大匹配(Berge 定理)。否則,我們可以將增廣路中所經過的邊進行修改,若其原來是匹配邊,則我們將其修改爲未匹配邊。若其原來是未匹配邊,則將其變爲匹配邊。這樣可以增加一對匹配。爲什麼呢?因爲增廣路上一定是匹配邊比未匹配邊少一條,可以根據定義想一想。有了這個性質,那麼我們就只需要找增廣路就行了。再根據Berge定理,就找到了最大匹配。看兩幅圖來感受一下尋找增廣路增加匹配的例子:
圖1 圖2
我們選擇從左邊的頂點集出發,第一個未匹配的頂點是 4,於是我們從4開始走交替路, 4->7->1->6, 到達6時沒有路可以走了,但此時沒有到達未匹配頂點,因此,從4出發沒有找到增廣路。所以我們從5出發, 5->9->2->10,這個時候我們找到了一條增廣路,把5-9, 2-10由未匹配邊變爲匹配邊,把2 - 9由匹配邊變爲未匹配邊,看看這個時候是不是多了一條匹配?由於左邊頂點集已經沒有未匹配的頂點能擴展增廣路,故此時匹配已經達到最大。
hdu 2063 是一道入門級的二分圖匹配。實現算法中圖的存儲結構選擇了鄰接表。
#include<stdio.h>
#include<string.h>
#include<vector>
using namespace std;
const int MAX = 2048;
vector<int> G[MAX];
typedef vector<int>::iterator iterator_vi;
int matched[MAX];
int vis[MAX];
int n, m, k;
bool dfs(int u)
{
for(iterator_vi i = G[u].begin(); i != G[u].end(); i++)
{
int v = *i;
if(!vis[v])
{
vis[v] = 1;
if(matched[v] == -1 || dfs(matched[v]))
{
// printf("%d %d\n", u, v);
matched[u] = v;
matched[v] = u;
return true;
}
}
}
return false;
}
int hungrain()
{
int ret = 0;
memset(matched, -1, sizeof(matched));
for(int i = 1; i <= m; i++)
{
if(matched[i] == -1)
{
memset(vis, 0, sizeof(vis));
if(dfs(i))
ret++;
}
}
return ret;
}
int main()
{
freopen("in.txt", "r", stdin);
while(~scanf("%d", &k))
{
if(k == 0) break;
scanf("%d%d", &m, &n);
for(int i = 1; i <= m; i++)
G[i].clear();
for(int i = 0; i < k; i++)
{
int a, b;
scanf("%d%d", &a, &b);
b += m;
G[a].push_back(b);
}
printf("%d\n", hungrain());
}
return 0;
}