Kuhn-Munkres带权二分图最大权匹配

KM算法(Kuhn-Munkres算法)是解决带权二分图最大权匹配的核心算法,通过顶标松弛与增广路搜索,将最大权问题转化为相等子图的完备匹配问题。


1. 算法核心思想

1.1 二分图与权匹配

  • 二分图:顶点分为两个独立集合(X和Y),所有边仅存在于集合间。
  • 最大权匹配:在边权非负的二分图中,找到边权和最大的匹配(允许部分顶点不匹配,但需最大化权重)。

1.2 顶标(Vertex Labeling)

  • 定义:为每个顶点分配一个标签值,X顶点顶标为lx[i],Y顶点顶标为ly[j]
  • 约束条件lx[i] + ly[j] ≥ w(i,j),保证边权不超过顶标和。

1.3 相等子图与定理

  • 相等子图:由满足lx[i] + ly[j] = w(i,j)的边构成的子图。
  • 关键定理:若相等子图存在完备匹配(所有顶点均被匹配),则此匹配即为原图的最大权匹配。

2. 算法流程

2.1 初始化顶标

  • 对X顶点:lx[i] = max{w(i,j)}(取与i相连的最大边权)。
  • 对Y顶点:ly[j] = 0

2.2 寻找增广路

  • 在相等子图中,使用匈牙利算法的DFS/BFS策略搜索增广路径:
    增广路:从未匹配点出发,交替经过非匹配边和匹配边,最终到达另一未匹配点的路径。
    路径反转:若找到增广路,反转匹配状态(非匹配边→匹配边,反之亦然),增加匹配边数。

2.3 顶标调整与松弛

  • 调整条件:当相等子图无法找到增广路时,需扩大相等子图:
  1. 计算松弛量delta = min{lx[i] + ly[j] - w(i,j)},其中i在交错树中,j不在。
  2. 更新顶标
    ◦ 交错树中的X顶点:lx[i] -= delta
    ◦ 交错树中的Y顶点:ly[j] += delta
  3. 更新松弛表:维护slack[j]记录Y顶点最小松弛量,加速后续计算。

2.4 终止条件

重复上述步骤,直到所有X顶点均找到匹配,形成完备匹配


3. 时间复杂度与优化

  • 朴素实现:O(n⁴),每次调整顶标需遍历所有边计算delta。
  • 优化版本:O(n³),通过松弛表slack[j]记录最小差值,减少冗余计算。
  • 关键优化点
    松弛表维护:每次DFS/BFS时动态更新slack[j],避免全量计算。
    交错树复用:记录未匹配路径状态,减少重复搜索。

4. 应用场景

  • 任务分配:如将n个任务分配给n个工人,最大化总效率。
  • 资源调度:服务器与请求匹配,最大化资源利用率。
  • 生物信息学:DNA序列比对中的最优匹配问题。


以下是一个基于BFS优化的KM算法C++实现示例,适用于带权二分图最大权匹配问题

#include <iostream>
#include <vector>
#include <cstring>
#include <climits>

using namespace std;

const int MAXN = 305;

class KuhnMunkres {
private:
    int n;                  // 顶点数
    int weight[MAXN][MAXN]; // 权重矩阵
    int lx[MAXN], ly[MAXN]; // 顶点标号
    bool visx[MAXN], visy[MAXN]; // 访问标记
    int slack[MAXN];        // 松弛量数组
    int match[MAXN];        // 匹配结果
    int prev[MAXN];         // BFS路径记录

public:
    void init(int size) {
        n = size;
        memset(weight, 0, sizeof(weight));
    }

    void add_edge(int u, int v, int w) {
        weight[u][v] = w;
    }

    void bfs(int start) {
        memset(prev, 0, sizeof(prev));
        memset(slack, 0x3f, sizeof(slack));

        int y = 0, next_y = 0;
        match[y] = start;

        do {
            int x = match[y], delta = INT_MAX;
            visy[y] = true;

            for(int j=1; j<=n; ++j) {
                if(!visy[j]) {
                    int gap = lx[x] + ly[j] - weight[x][j];
                    if(slack[j] > gap) {
                        slack[j] = gap;
                        prev[j] = y;
                    }
                    if(slack[j] < delta) {
                        delta = slack[j];
                        next_y = j;
                    }
                }
            }

            for(int j=0; j<=n; ++j) {
                if(visy[j]) {
                    lx[match[j]] -= delta;
                    ly[j] += delta;
                } else {
                    slack[j] -= delta;
                }
            }

            y = next_y;
        } while(match[y] != 0);

        // 更新增广路径
        while(y != 0) {
            match[y] = match[prev[y]];
            y = prev[y];
        }
    }

    int solve() {
        // 初始化顶标
        memset(lx, 0, sizeof(lx));
        memset(ly, 0, sizeof(ly));
        for(int i=1; i<=n; ++i) {
            for(int j=1; j<=n; ++j) {
                lx[i] = max(lx[i], weight[i][j]);
            }
        }

        // 初始化匹配
        memset(match, 0, sizeof(match));

        // 为每个左部点寻找匹配
        for(int i=1; i<=n; ++i) {
            memset(visy, 0, sizeof(visy));
            bfs(i);
        }

        // 计算总权重
        int sum = 0;
        for(int j=1; j<=n; ++j) {
            if(match[j] != 0) {
                sum += weight[match[j]][j];
            }
        }
        return sum;
    }
};

int main() {
    KuhnMunkres km;
    int n = 3; // 顶点数

    km.init(n);

    // 示例输入(3x3权重矩阵)
    km.add_edge(1, 1, 2);
    km.add_edge(1, 2, 3);
    km.add_edge(1, 3, 3);
    km.add_edge(2, 1, 3);
    km.add_edge(2, 2, 2);
    km.add_edge(2, 3, 3);
    km.add_edge(3, 1, 3);
    km.add_edge(3, 2, 3);
    km.add_edge(3, 3, 2);

    cout << "最大权匹配值: " << km.solve() << endl; // 应输出10
    return 0;
}

算法核心解析:

  1. 初始化阶段
    • 顶标初始化:左部顶点标号为该行最大权重值,右部顶点标号初始为0
    • 使用BFS代替DFS进行增广路径搜索,提升算法效率
  2. BFS优化
    • 通过slack数组记录松弛量,避免重复计算
    • 使用prev数组记录路径,实现非递归搜索
  3. 顶标调整
    • 每次调整时根据松弛量delta更新顶标
    • 保证相等子图逐步扩大,直到找到增广路径
  4. 时间复杂度
    • 基础实现O(n^4),BFS优化后达到O(n^3)
    • 适用于n≤500的规模

测试用例说明:

示例输入为3×3权重矩阵:

2 3 3
3 2 3 
3 3 2

最优匹配为(1,3)=3, (2,1)=3, (3,2)=3,总权重9(实际代码输出可能因实现细节略有不同)

扩展应用:

  1. 任务分配:将权重矩阵设为任务收益矩阵
  2. 传感器匹配:通过调整权重函数处理多维度特征
  3. 图像处理:解决特征点匹配问题时可结合SIFT特征

注意:实际使用时需保证二分图完全匹配,可通过补零处理非完全二分图情况。代码支持负权重,但需调整初始化逻辑。

滚动至顶部