1. 程式人生 > >二分圖最佳完美匹配

二分圖最佳完美匹配

華電北風吹
天津大學認知計算與應用重點實驗室
日期:2016-07-15

一、二分圖的幾個相關定義:
最大匹配:匹配邊最多。
完美匹配:所有的頂點全部是匹配點。
最佳完美匹配:邊有權重,完美匹配中匹配邊權值和最大。

二、最佳完美匹配中需要了解的幾個定義
頂標:給每個頂點賦一個數值,成為該頂點的定標。
可行頂標:如果對於所有的邊,滿足此邊兩端的兩個頂點的定標和不小於此邊權重,則稱這一組頂標為可行頂標。
相等子圖:在當前頂標下,滿足邊兩端的兩個頂點的定標和正好等於此邊權重的所有邊,所構成的子圖。

三、KM演算法(Kuhn-Munkres演算法,匈牙利演算法)
對於二分圖的一部,初始定標為當前結點所連線邊權重最大值,另一部初始定標全為0,然後對於每個節點進行匹配。若相等子圖存在可擴充套件路徑則更新匹配關係,換下一個節點;若不存在擴充套件路徑,則更新定標,使匈牙利樹增加一條邊即可(由匈牙利樹的性質可知,這條邊必是從根節點同側匹配點到另一側非匹配點,根據這個更新對應點頂標即可)。具體更新規則見模板程式碼update部分。

四、參考程式碼

#include <iostream>
#include <algorithm>
using namespace std;

#define maxn 5

int W[maxn][maxn], n;
int Lx[maxn];
int Ly[maxn];
int Left[maxn];
bool S[maxn], T[maxn];

bool Match(int i)
{
    S[i] = true;
    for (int j = 1; j <= n; j++)
    {
        if (Lx[i] + Ly[j] == W[i][j] && !T[j])
        {
            T[j] = true
; if (!Left[j] || Match(Left[j])) { Left[j] = i; return true; } } } return false; } void Update() { int a = 1 << 30; for (int i = 1; i <= n; i++) { if (S[i]) { for (int
j = 1; j <= n; j++) { if (!T[j]) { a = min(a, Lx[i] + Ly[j] - W[i][j]); } } } } for (int i = 1; i <= n; i++) { if (S[i]) Lx[i] -= a; if (T[i]) Ly[i] += a; } } void KM() { for (int i = 1; i <= n; i++) { Left[i] = 0; Lx[i] = 0; Ly[i] = 0; for (int j = 1; j <= n; j++) { Lx[i] = max(Lx[i], W[i][j]); } } for (int i = 1; i <= n; i++) { while (true) { memset(S, 0, sizeof(S)); memset(T, 0, sizeof(T)); if (Match(i)) break; else Update(); } } } int main() { return 0; }

五、擴充套件問題
覆蓋集:對於每條邊,至少要有一個點要被選中。
獨立集:對於每條邊,至少要有一個點不被選中。
二分圖的最大獨立集:選擇儘量多的點,使得任意兩個節點不相鄰(即任意一條邊的兩個端點不會同時被選中)。
二分圖的最小覆蓋::選擇儘量少的點,使得每條邊至少有一個端點被選中。
並且有最小覆蓋數加最大獨立集個數等於節點個數。
棋盤中的車,指派問題,最小集合覆蓋