KM演算法

taijinvjin發表於2024-08-18

KM

const int INF = 0x3f3f3f3f;  
const int N = 305;  
  
int n, m, match[N], pre[N];  
bool vis[N];  
int favor[N][N];  
int val1[N], val2[N], slack[N];  
  
void bfs(int p) {  
    memset(pre, 0, sizeof pre);  
    memset(slack, INF, sizeof slack);  
  
    match[0] = p;  
  
    int x = 0, nex = 0;  
    do {  
        vis[x] = true;  
  
        int y = match[x], d = INF;  
  
        // 對於當前節點y,bfs有連邊的下一點  
        for (int i = 1; i <= m; i++) {  
            if (!vis[i]) {  
                if (slack[i] > val1[y] + val2[i] - favor[y][i]) {  
                    slack[i] = val1[y] + val2[i] - favor[y][i];  
                    pre[i] = x;  
                }  
                if (slack[i] < d) {  
                    d = slack[i];  
                    nex = i;  
                }  
            }  
        }  
  
        for (int i = 0; i <= m; i++) {  
            if (vis[i])  
                val1[match[i]] -= d, val2[i] += d;  
            else  
                slack[i] -= d;  
        }  
  
        x = nex;  
  
    } while (match[x]);  
  
    // pre陣列對bfs訪問路徑進行記錄,在最後一併改變match  
    while (x) {  
        match[x] = match[pre[x]];  
        x = pre[x];  
    }  
}  
  
int KM() {  
    memset(match, 0, sizeof match);  
    memset(val1, 0, sizeof val1);  
    memset(val2, 0, sizeof val2);  
    for (int i = 1; i <= n; i++) {  
        memset(vis, false, sizeof vis);  
        bfs(i);  
    }  
    int res = 0;  
    for (int i = 1; i <= m; i++)  
        res += favor[match[i]][i];  
    return res;  
}

這個演算法是用於解決二分圖最大權匹配問題的Kuhn-Munkres演算法(簡稱KM演算法)。KM演算法透過逐步調整頂標(也稱為勢)來尋找最大權匹配。以下是對程式碼的詳細解釋:

變數解釋

  • nm:分別表示二分圖的兩部分頂點數。
  • match[N]:記錄匹配資訊,match[i] 表示右邊第 i 個頂點匹配的左邊頂點。
  • pre[N]:記錄增廣路徑的前驅節點。
  • vis[N]:記錄節點是否在當前增廣路徑中被訪問過。
  • favor[N][N]:表示二分圖中邊的權重。
  • val1[N]val2[N]:分別表示左邊和右邊頂點的頂標(勢)。
  • slack[N]:記錄每個右邊頂點與當前增廣路徑的最小差值。

函式解釋

bfs(int p)

這個函式用於尋找從左邊第 p 個頂點開始的增廣路徑。

  1. 初始化

    • memset(pre, 0, sizeof pre);:初始化前驅節點陣列。
    • memset(slack, INF, sizeof slack);:初始化 slack 陣列為無窮大。
    • match[0] = p;:將 p 作為起點。
  2. 迴圈尋找增廣路徑

    • do 迴圈:
      • vis[x] = true;:標記當前節點 x 為已訪問。
      • int y = match[x], d = INF;:獲取當前節點的匹配節點 y,並初始化最小差值 d 為無窮大。
      • 對於當前節點 y,遍歷所有右邊節點 i
        • 如果 i 未被訪問:
          • 更新 slack[i]val1[y] + val2[i] - favor[y][i]
          • 更新前驅節點 pre[i]x
          • 如果 slack[i] 小於 d,則更新 dnex
      • 更新所有節點的頂標:
        • 如果節點 i 被訪問,則 val1[match[i]] -= dval2[i] += d
        • 否則,slack[i] -= d
      • x = nex;:更新當前節點為 nex
    • while (match[x]);:直到找到未匹配的節點。
  3. 更新匹配資訊

    • while (x) 迴圈:
      • match[x] = match[pre[x]];:更新匹配資訊。
      • x = pre[x];:回溯前驅節點。

KM()

這個函式是KM演算法的主函式,用於求解二分圖的最大權匹配。

  1. 初始化

    • memset(match, 0, sizeof match);:初始化匹配陣列。
    • memset(val1, 0, sizeof val1);:初始化左邊頂標。
    • memset(val2, 0, sizeof val2);:初始化右邊頂標。
  2. 尋找增廣路徑

    • 對於每個左邊節點 i,呼叫 bfs(i) 尋找增廣路徑。
  3. 計算最大權匹配

    • 遍歷所有右邊節點 i,累加匹配邊的權重 favor[match[i]][i]

總結

KM演算法透過逐步調整頂標來尋找最大權匹配。bfs 函式用於尋找增廣路徑,並在找到增廣路徑後更新匹配資訊。KM 函式則是主函式,負責初始化和呼叫 bfs 函式,最終計算並返回最大權匹配的值。

相關文章