1. 程式人生 > 實用技巧 >匈牙利演算法學習筆記

匈牙利演算法學習筆記

眾所周知,二分圖匹配是二分圖理論中的基礎,而匈牙利演算法是一種求解它的基本演算法。事實上,匈牙利演算法是一個十分簡潔的演算法,簡介到讓人感到驚詫。下面就讓我們瞭解一下這個神奇的演算法。

先看幾個定義:

  • 匹配 匹配是一個邊的集合,其中任意兩條邊都沒有公共端點。
  • 最大匹配 顧名思義,包含邊數最多的匹配
  • 交錯路 匹配邊和非匹配邊依次出現的一條路
  • 增廣路 從非匹配邊出發,以非匹配邊結束的交錯路(增廣路長度一定是奇數)

你可能已經被這幾個定義搞懵了不過沒關係,慢慢看下去你就懂了。

匈牙利演算法的基本思想,就是不斷地擴大匹配的集合。事實上,他就是一個不斷嘗試讓匹配邊數加一的過程。初始狀態下,所有的邊都是非匹配邊,而當演算法結束時,我們再也不能讓匹配邊數變多了,最大匹配數也就求出來了,還能附帶求出一種方案。

匈牙利演算法基於兩個性質:

  • 一個匹配\(M\)是最大匹配,當且僅當圖中不存在增廣路。
  • 從某一點出發,如果某個時刻找不到增廣路,那麼你將永遠找不到從該點出發的最短路。

性質1的必要性是顯然的(因為增廣路長度是奇數),我們先把這兩條性質拋開,先來看看這個演算法的過程。

我們首先給二分圖定向(名字挺高大上,其實就是劃分出左部和右部)。列舉每一個左部點(如果這個點已經被匹配,就直接跳過),如果找不到增廣路就算了,如果找到了增廣路,我們就把這條路徑上所有邊的狀態取反,於是我們就得到了一種大小+1的匹配。結合程式碼理解一下

int vis[mxn],mch[mxn];//match
inline bool hungary(int x){
    if(vis[x])return 0;vis[x]=1;
//vis記錄左部點右部點皆可
    for(int i=first[x];i;i=nxt[i]) //邊是單向邊
        if(!mch[to[i]] || hungary(mch[to[i]]))return mch[to[i]]=x,1;
    return 0;
}

//main函式
int ans=0;
for(int i=1;i<=n;++i)memset(vis,0,sizeof(vis)),ans+=hungary(i);

如果你沒看懂以上程式碼,那隻能是心理原因 我們回過頭看這兩個性質,性質1顯然是演算法的基礎,而性質2確保了每個點只搜一次。至此我們已經完全理解了匈牙利演算法的內在邏輯。

有沒有覺得這兩個性質很神奇?如果感興趣可以看看這篇論文,挺好理解的,如果不知道什麼是對稱差可以百度一下。

另外還有bfs實現的版本,據說常數更小。

以上。