匈牙利算法


0 - 相關概念

0.1 - 匈牙利算法

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

0.2 - 二分圖

  若圖$G$的結點集合$V(G)$可以分成兩個非空子集$V_1$和$V_2$,並且圖$G$的任意邊$xy$關聯的兩個結點$x$和$y$分別屬於這兩個子集,則$G$是二分圖。

1 - 基本思想

  1. 找到當前結點$a$可以匹配的對象$A$,若該對象$A$已被匹配,則轉入第3步,否則轉入第2步
  2. 將該對象$A$的匹配對象記為當前對象$a$,轉入第6步
  3. 尋找該對象$A$已經匹配的對象$b$,尋求其$b$是否可以匹配另外的對象$B$,如果可以,轉入第4步,否則,轉入第5步
  4. 將匹配對象$b$更新為另一個對象$B$,將對象$A$的匹配對象更新為$a$,轉入第6步
  5. 結點$a$尋求下一個可以匹配的對象,如果存在,則轉入第1步,否則說明當前結點$a$沒有可以匹配的對象,轉入第6步
  6. 轉入下一結點再轉入第1步

2 - 樣例解析

  上面的基本思想看完肯定一頭霧水(很大程度是受限於我的表達能力),下面通過POJ 1274來就匈牙利算法做一個詳細的樣例解析。

2.1 - 題目大意

  農場主John有$N$頭奶牛和$M$個畜欄,每一頭奶牛需要在特定的畜欄才能產奶。第一行給出$N$和$M$,接下來$N$行每行代表對應編號的奶牛,每行的第一個數值$T$表示該奶牛可以在多少個畜欄產奶,而后的$T$個數值為對應畜欄的編號,最后輸出一行,表示最多可以讓多少頭奶牛產奶。

2.1 - 輸入樣例

5 5
2 2 5
3 2 3 4
2 1 5
3 1 2 5
1 2

2.2 - 匈牙利算法解題思路

2.2.1 - 構造二分圖

  根據輸入樣例構造如下二分圖,藍色結點表示奶牛,黃色結點表示畜欄,連線表示對應奶牛能在對應畜欄產奶。

               

2.2.2 - 模擬算法流程
  • 為結點1(奶牛)分配畜欄,分配畜欄2(如圖(a)加粗紅邊所示)
  • 為結點2(奶牛)分配畜欄,由於畜欄2已經被分配給結點1(奶牛),所以尋求結點1(奶牛)是否能夠分配別的畜欄,以把畜欄2騰給結點2(奶牛)。結點2(奶牛)在畜欄5也可以產奶,因此,給結點1(奶牛)分配畜欄5(如圖(b)加粗黃邊所示),然后把畜欄2給結點2(奶牛)(如圖(b)加粗紅邊所示)
  • 為結點3(奶牛)分配畜欄,分配畜欄1(如圖(c)加粗紅邊所示)
  • 為結點4(奶牛)分配畜欄,由於畜欄1已經被分配給結點3(奶牛),所以尋求結點3(奶牛)是否能夠分配別的畜欄,但下一個可以分配給結點3(奶牛)的畜欄5已經被分配給結點1(奶牛),因此尋求結點1(奶牛)是否能夠分配別的畜欄,嘗試給結點1(奶牛)分配畜欄2,此前畜欄2已經被結點2(奶牛)占有了,因此需要嘗試給結點2(奶牛)分配下一個畜欄3 (如圖(d)加粗紅邊所示)
  • 為結點5(奶牛)分配畜欄,由於畜欄2已經分配給結點1(奶牛),所以嘗試為結點1(奶牛)分配下一個畜欄5,之前畜欄5被結點3(奶牛)所占,需要為其分配另一個畜欄,嘗試為其分配畜欄1,而畜欄1之前被結點4(奶牛)所占,需要為其分配另一個畜欄,發現能夠給其分配的畜欄1、2、5均已經被占了,產生矛盾(如圖(e)加粗黑邊所示),此輪分配失敗
  • 最多有4頭奶牛(編號為1、2、3、4的奶牛)分配到了畜欄(如圖(d)加粗紅邊所示)
 

 

 2.2.3 - 代碼實現
// 導入相關庫
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
// 定義需要的變量
#define N 205 
#define M 205

bool line[N][M]; //表示對應位置的奶牛和畜欄是否有邊
int hascow[M]; //表示對應位置的畜欄被分配到哪一只編號的奶牛(0是未分配)
bool used[M]; //表示對應位置的畜欄是否已經被走過了,用於確保尋求增廣路不走重復結點
int n, m;
//分配
bool find(int x) {
    for (int i=1; i<=m; i++) { //遍歷所有畜欄
        //如果該奶牛可以分配到這個畜欄並且該畜欄未被使用
        if (line[x][i] && !used[i]) {
            used[i] = true; //標記該畜欄當前循環被使用了
            //如果該畜欄沒有被分配或者可以通過給原本占有該畜欄的奶牛分配其它畜欄
            if (!hascow[i] || find(hascow[i])) { 
                //將該畜欄分配給該奶牛
                hascow[i] = x;
                return true; //分配成功
            }
        }
    }
    return false; //分配失敗
}            
int main() {
    int t, x, res;
    while (~scanf("%d%d", &n, &m)) {
     //初始化變量,然后根據輸入格式構建二分圖
        memset(line, 0, sizeof(line));
        for (int i=1; i<=n; i++) {
            scanf("%d", &t);
            for (int j=1; j<=t; j++) {
                scanf("%d", &x);
                line[i][x] = true;
            }    
        }
        res = 0;
        memset(hascow, 0, sizeof(hascow));
        for (int i=1; i<=n; i++) {
            memset(used, 0, sizeof(used));
            if (find(i)) res ++; //如果可以成功給該奶牛分配畜欄,可以分配的奶牛數量+1
        }
        printf("%d\n", res);
    }
    return 0;
}

3 - 參考材料

https://blog.csdn.net/tanzhangwen/article/details/8262006

https://vjudge.net/problem/10500/origin

https://www.cnblogs.com/qq-star/p/4633101.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM