最大权值匹配算法

算法描述

最大权值匹配算法(Maximum Weight Matching Algorithm)指的是在一个带权图中,选取一些边,并使这些边两端所连的节点不同,使这些边上的权值之和最大化的一个问题。

常用的算法有两种:匈牙利算法和KM算法。

题目描述

以下是一道最大权值匹配算法题目:

有一个公司需要为 N 个员工分配任务,对于员工 i,可以完成任务 j 并获得收益 w(i, j)。为了使公司得到最大的收益,你需要将 N 个员工分配到 N 个任务上,使得总收益最大。注意,每个员工只能被分配一项任务,每项任务也只能由一个员工完成。

请编写一个优化算法,计算出最大收益,并输出员工和任务的匹配情况。

输入格式:
第一行一个整数 N,表示员工和任务的数量。
接下来 N 行,每行 N 个整数,表示 w(i, j),表示第 i 个员工完成第 j 个任务可以获得的收益。

输出格式:
第一行输出最大收益,第二行输出员工和任务的匹配情况,每行两个数 i 和 j,表示员工 i 被分配到任务 j 上。

解答

匈牙利算法

匈牙利算法是解决二分图最大权值匹配问题的一种经典算法。在一个二分图中,每个节点都被划分为左右两个部分,我们需要在左部节点和右部节点之间建立一一对应的匹配,使得匹配边的权值之和最大。

匈牙利算法的基本思路是通过不断增广匹配来寻找最大权值匹配。它的步骤如下:

初始化:将所有匹配边的权值设为0,找到所有未匹配的左部节点,将它们依次作为增广路径的起点。

找增广路径:对于每个未匹配的左部节点,尝试通过DFS或BFS寻找一条增广路径。具体来说,我们从该左部节点开始,依次考虑与它相邻的右部节点,如果该右部节点未匹配或存在未访问过的增广路径,就将其匹配到当前左部节点,否则我们尝试沿着当前右部节点已匹配的边继续寻找增广路径,直到找到一条新的未匹配的右部节点为止。如果找到了一条增广路径,就将该路径上所有已匹配的边反转成未匹配的边,将该路径上所有未匹配的边匹配起来,然后返回步骤2。

最大匹配:重复执行步骤2,直到没有增广路径可以找到为止。此时所有已匹配的边就组成了最大权值匹配。

匈牙利算法的时间复杂度为 O ( n 3 ) O(n^3) O(n3),其中 n n n 是二分图中节点的总数。具体来说,每次寻找增广路径需要遍历整个图,而最多需要增广 n n n 次才能找到最大匹配。

代码

以下是使用 C++ 实现最大权值匹配算法的代码示例:

#include <iostream>
#include <cstring>
#include <string>
#include <format>
#include <string_view>
using namespace std;

const int MAXN = 1005; // 最大点数
const int INF = 0x3f3f3f3f;

int weight[MAXN][MAXN];  // 二分图的邻接矩阵
int lx[MAXN], ly[MAXN];  // 顶标
bool sx[MAXN], sy[MAXN]; // 是否在匹配中
int match[MAXN];         // 记录右部点的匹配左部点编号,-1 表示未匹配

int n; // 点的总数,左部点编号 0~n-1,右部点编号 n~2n-1

template <typename T>
string GetArrInfo(T &arr)
{
    string res = "";
    for (size_t i = 0; i < n; i++)
    {
        res += to_string(arr[i]);
        if (i != n - 1)
            res += ' ';
    }
    return res;
}

bool dfs(int u, bool isUpSearch = false)
{
    sx[u] = true;
    cout << "sx: " + GetArrInfo(sx) + "  sy: " + GetArrInfo(sy) + "  lx: " + GetArrInfo(lx) + "  ly: " + GetArrInfo(ly) + "  match:" + GetArrInfo(match) + " --change sx u=" + to_string(u) + (isUpSearch ? " --UpSearch" : "") + "\n";
    for (int v = 0; v < n; v++)
        if (!sy[v] && lx[u] + ly[v] == weight[u][v])
        {
            sy[v] = true;
            cout << "sx: " + GetArrInfo(sx) + "  sy: " + GetArrInfo(sy) + "  lx: " + GetArrInfo(lx) + "  ly: " + GetArrInfo(ly) + "  match:" + GetArrInfo(match) +
                        " --change sy " + "lx[" + to_string(u) + "] + ly[" + to_string(v) + "]=" + to_string(weight[u][v]) + " \n";
            if (match[v] == -1 || dfs(match[v], true))
            {
                match[v] = u;
                cout << "sx: " + GetArrInfo(sx) + "  sy: " + GetArrInfo(sy) + "  lx: " + GetArrInfo(lx) + "  ly: " + GetArrInfo(ly) + "  match:" + GetArrInfo(match) + " --change match \n";
                return true;
            }
        }
    return false;
}

int HungaryMatch(bool maxsum = true)
{
    int i, j;
    if (!maxsum)
    {
        for (i = 0; i < n; i++)
            for (j = 0; j < n; j++)
                weight[i][j] = -weight[i][j];
    }

    // 初始化标号
    for (i = 0; i < n; i++)
    {
        lx[i] = -0x1FFFFFFF;
        ly[i] = 0;
        for (j = 0; j < n; j++)
            if (lx[i] < weight[i][j])
                lx[i] = weight[i][j];
    }

    memset(match, -1, sizeof(match));
    for (int u = 0; u < n; u++)
    {
        cout << "Left Point Index:" + to_string(u) + "\n";
        while (true)
        {
            // 重置sx,sy状态 都置为未访问
            memset(sx, 0, sizeof(sx));
            memset(sy, 0, sizeof(sy));

            // 深度优先搜索DFS
            bool is_find = dfs(u);
            cout << "sx: " + GetArrInfo(sx) + "  sy: " + GetArrInfo(sy) + "  lx: " + GetArrInfo(lx) + "  ly: " + GetArrInfo(ly) + "  match:" + GetArrInfo(match) + " --After dfs isfind=" + to_string(is_find) + " \n";
            if (is_find)
                break;

            // 修改标号  DFS未能找到可分配项
            // 对已访问的左侧节点,求他们与未访问的右侧节点的权值最小差值
            int dx = 0x7FFFFFFF;
            for (i = 0; i < n; i++)
                if (sx[i])
                    for (j = 0; j < n; j++)
                        if (!sy[j])
                            dx = min(lx[i] + ly[j] - weight[i][j], dx);

            // 对DFS搜索路径中访问过的左侧节点和右侧节点 更新标号 lx, ly  
            // 主要用于便于递归遍历时查找 lx[u] + ly[v] == weight[u][v] 且 ly[v]==0的匹配项
            for (i = 0; i < n; i++)
            {
                if (sx[i])
                    lx[i] -= dx;
                if (sy[i])
                    ly[i] += dx;
            }

            cout << "sx: " + GetArrInfo(sx) + "  sy: " + GetArrInfo(sy) + "  lx: " + GetArrInfo(lx) + "  ly: " + GetArrInfo(ly) + "  match:" + GetArrInfo(match) + " -- change lx and ly, dx = " + to_string(dx) + "\n";
        }
    }

    int sum = 0;
    for (i = 0; i < n; i++)
        sum += weight[match[i]][i];

    if (!maxsum)
    {
        sum = -sum;
        for (i = 0; i < n; i++)
            for (j = 0; j < n; j++)
                weight[i][j] = -weight[i][j]; // 如果需要保持 weight [ ] [ ] 原来的值,这里需要将其还原
    }
    return sum;
}

/*
5
3 4 6 4 9
6 4 5 3 8
7 5 3 4 2
6 3 2 2 5
8 4 5 4 7
//执行bestmatch (true) ,结果为 29
*/

int main()
{
    // 读入二分图
    cin >> n;
    for (int i = 0; i < n; i++)
    {
        for (int j = 0; j < n; j++)
        {
            cin >> weight[i][j];
        }
    }

    int ans = HungaryMatch();
    cout << ans << endl;

    return 0;
}

执行结果(重点看 --change match的输出,可以便于理解):

5
3 4 6 4 9
6 4 5 3 8
7 5 3 4 2
6 3 2 2 5
8 4 5 4 7

Left Point Index:0
sx: 1 0 0 0 0  sy: 0 0 0 0 0  lx: 9 8 7 6 8  ly: 0 0 0 0 0  match:-1 -1 -1 -1 -1 --change sx u=0
sx: 1 0 0 0 0  sy: 0 0 0 0 1  lx: 9 8 7 6 8  ly: 0 0 0 0 0  match:-1 -1 -1 -1 -1 --change sy lx[0] + ly[4]=9
sx: 1 0 0 0 0  sy: 0 0 0 0 1  lx: 9 8 7 6 8  ly: 0 0 0 0 0  match:-1 -1 -1 -1 0 --change match
sx: 1 0 0 0 0  sy: 0 0 0 0 1  lx: 9 8 7 6 8  ly: 0 0 0 0 0  match:-1 -1 -1 -1 0 --After dfs isfind=1
Left Point Index:1
sx: 0 1 0 0 0  sy: 0 0 0 0 0  lx: 9 8 7 6 8  ly: 0 0 0 0 0  match:-1 -1 -1 -1 0 --change sx u=1
sx: 0 1 0 0 0  sy: 0 0 0 0 1  lx: 9 8 7 6 8  ly: 0 0 0 0 0  match:-1 -1 -1 -1 0 --change sy lx[1] + ly[4]=8
sx: 1 1 0 0 0  sy: 0 0 0 0 1  lx: 9 8 7 6 8  ly: 0 0 0 0 0  match:-1 -1 -1 -1 0 --change sx u=0 --UpSearch
sx: 1 1 0 0 0  sy: 0 0 0 0 1  lx: 9 8 7 6 8  ly: 0 0 0 0 0  match:-1 -1 -1 -1 0 --After dfs isfind=0
sx: 1 1 0 0 0  sy: 0 0 0 0 1  lx: 7 6 7 6 8  ly: 0 0 0 0 2  match:-1 -1 -1 -1 0 -- change lx and ly, dx = 2
sx: 0 1 0 0 0  sy: 0 0 0 0 0  lx: 7 6 7 6 8  ly: 0 0 0 0 2  match:-1 -1 -1 -1 0 --change sx u=1
sx: 0 1 0 0 0  sy: 1 0 0 0 0  lx: 7 6 7 6 8  ly: 0 0 0 0 2  match:-1 -1 -1 -1 0 --change sy lx[1] + ly[0]=6
sx: 0 1 0 0 0  sy: 1 0 0 0 0  lx: 7 6 7 6 8  ly: 0 0 0 0 2  match:1 -1 -1 -1 0 --change match
sx: 0 1 0 0 0  sy: 1 0 0 0 0  lx: 7 6 7 6 8  ly: 0 0 0 0 2  match:1 -1 -1 -1 0 --After dfs isfind=1
Left Point Index:2
sx: 0 0 1 0 0  sy: 0 0 0 0 0  lx: 7 6 7 6 8  ly: 0 0 0 0 2  match:1 -1 -1 -1 0 --change sx u=2
sx: 0 0 1 0 0  sy: 1 0 0 0 0  lx: 7 6 7 6 8  ly: 0 0 0 0 2  match:1 -1 -1 -1 0 --change sy lx[2] + ly[0]=7
sx: 0 1 1 0 0  sy: 1 0 0 0 0  lx: 7 6 7 6 8  ly: 0 0 0 0 2  match:1 -1 -1 -1 0 --change sx u=1 --UpSearch
sx: 0 1 1 0 0  sy: 1 0 0 0 1  lx: 7 6 7 6 8  ly: 0 0 0 0 2  match:1 -1 -1 -1 0 --change sy lx[1] + ly[4]=8
sx: 1 1 1 0 0  sy: 1 0 0 0 1  lx: 7 6 7 6 8  ly: 0 0 0 0 2  match:1 -1 -1 -1 0 --change sx u=0 --UpSearch
sx: 1 1 1 0 0  sy: 1 0 0 0 1  lx: 7 6 7 6 8  ly: 0 0 0 0 2  match:1 -1 -1 -1 0 --After dfs isfind=0
sx: 1 1 1 0 0  sy: 1 0 0 0 1  lx: 6 5 6 6 8  ly: 1 0 0 0 3  match:1 -1 -1 -1 0 -- change lx and ly, dx = 1
sx: 0 0 1 0 0  sy: 0 0 0 0 0  lx: 6 5 6 6 8  ly: 1 0 0 0 3  match:1 -1 -1 -1 0 --change sx u=2
sx: 0 0 1 0 0  sy: 1 0 0 0 0  lx: 6 5 6 6 8  ly: 1 0 0 0 3  match:1 -1 -1 -1 0 --change sy lx[2] + ly[0]=7
sx: 0 1 1 0 0  sy: 1 0 0 0 0  lx: 6 5 6 6 8  ly: 1 0 0 0 3  match:1 -1 -1 -1 0 --change sx u=1 --UpSearch
sx: 0 1 1 0 0  sy: 1 0 1 0 0  lx: 6 5 6 6 8  ly: 1 0 0 0 3  match:1 -1 -1 -1 0 --change sy lx[1] + ly[2]=5
sx: 0 1 1 0 0  sy: 1 0 1 0 0  lx: 6 5 6 6 8  ly: 1 0 0 0 3  match:1 -1 1 -1 0 --change match
sx: 0 1 1 0 0  sy: 1 0 1 0 0  lx: 6 5 6 6 8  ly: 1 0 0 0 3  match:2 -1 1 -1 0 --change match
sx: 0 1 1 0 0  sy: 1 0 1 0 0  lx: 6 5 6 6 8  ly: 1 0 0 0 3  match:2 -1 1 -1 0 --After dfs isfind=1
Left Point Index:3
sx: 0 0 0 1 0  sy: 0 0 0 0 0  lx: 6 5 6 6 8  ly: 1 0 0 0 3  match:2 -1 1 -1 0 --change sx u=3
sx: 0 0 0 1 0  sy: 0 0 0 0 0  lx: 6 5 6 6 8  ly: 1 0 0 0 3  match:2 -1 1 -1 0 --After dfs isfind=0
sx: 0 0 0 1 0  sy: 0 0 0 0 0  lx: 6 5 6 5 8  ly: 1 0 0 0 3  match:2 -1 1 -1 0 -- change lx and ly, dx = 1
sx: 0 0 0 1 0  sy: 0 0 0 0 0  lx: 6 5 6 5 8  ly: 1 0 0 0 3  match:2 -1 1 -1 0 --change sx u=3
sx: 0 0 0 1 0  sy: 1 0 0 0 0  lx: 6 5 6 5 8  ly: 1 0 0 0 3  match:2 -1 1 -1 0 --change sy lx[3] + ly[0]=6
sx: 0 0 1 1 0  sy: 1 0 0 0 0  lx: 6 5 6 5 8  ly: 1 0 0 0 3  match:2 -1 1 -1 0 --change sx u=2 --UpSearch
sx: 0 0 1 1 0  sy: 1 0 0 0 0  lx: 6 5 6 5 8  ly: 1 0 0 0 3  match:2 -1 1 -1 0 --After dfs isfind=0
sx: 0 0 1 1 0  sy: 1 0 0 0 0  lx: 6 5 5 4 8  ly: 2 0 0 0 3  match:2 -1 1 -1 0 -- change lx and ly, dx = 1
sx: 0 0 0 1 0  sy: 0 0 0 0 0  lx: 6 5 5 4 8  ly: 2 0 0 0 3  match:2 -1 1 -1 0 --change sx u=3
sx: 0 0 0 1 0  sy: 1 0 0 0 0  lx: 6 5 5 4 8  ly: 2 0 0 0 3  match:2 -1 1 -1 0 --change sy lx[3] + ly[0]=6
sx: 0 0 1 1 0  sy: 1 0 0 0 0  lx: 6 5 5 4 8  ly: 2 0 0 0 3  match:2 -1 1 -1 0 --change sx u=2 --UpSearch
sx: 0 0 1 1 0  sy: 1 1 0 0 0  lx: 6 5 5 4 8  ly: 2 0 0 0 3  match:2 -1 1 -1 0 --change sy lx[2] + ly[1]=5
sx: 0 0 1 1 0  sy: 1 1 0 0 0  lx: 6 5 5 4 8  ly: 2 0 0 0 3  match:2 2 1 -1 0 --change match
sx: 0 0 1 1 0  sy: 1 1 0 0 0  lx: 6 5 5 4 8  ly: 2 0 0 0 3  match:3 2 1 -1 0 --change match
sx: 0 0 1 1 0  sy: 1 1 0 0 0  lx: 6 5 5 4 8  ly: 2 0 0 0 3  match:3 2 1 -1 0 --After dfs isfind=1
Left Point Index:4
sx: 0 0 0 0 1  sy: 0 0 0 0 0  lx: 6 5 5 4 8  ly: 2 0 0 0 3  match:3 2 1 -1 0 --change sx u=4
sx: 0 0 0 0 1  sy: 0 0 0 0 0  lx: 6 5 5 4 8  ly: 2 0 0 0 3  match:3 2 1 -1 0 --After dfs isfind=0
sx: 0 0 0 0 1  sy: 0 0 0 0 0  lx: 6 5 5 4 6  ly: 2 0 0 0 3  match:3 2 1 -1 0 -- change lx and ly, dx = 2
sx: 0 0 0 0 1  sy: 0 0 0 0 0  lx: 6 5 5 4 6  ly: 2 0 0 0 3  match:3 2 1 -1 0 --change sx u=4
sx: 0 0 0 0 1  sy: 1 0 0 0 0  lx: 6 5 5 4 6  ly: 2 0 0 0 3  match:3 2 1 -1 0 --change sy lx[4] + ly[0]=8
sx: 0 0 0 1 1  sy: 1 0 0 0 0  lx: 6 5 5 4 6  ly: 2 0 0 0 3  match:3 2 1 -1 0 --change sx u=3 --UpSearch
sx: 0 0 0 1 1  sy: 1 0 0 0 0  lx: 6 5 5 4 6  ly: 2 0 0 0 3  match:3 2 1 -1 0 --After dfs isfind=0
sx: 0 0 0 1 1  sy: 1 0 0 0 0  lx: 6 5 5 3 5  ly: 3 0 0 0 3  match:3 2 1 -1 0 -- change lx and ly, dx = 1
sx: 0 0 0 0 1  sy: 0 0 0 0 0  lx: 6 5 5 3 5  ly: 3 0 0 0 3  match:3 2 1 -1 0 --change sx u=4
sx: 0 0 0 0 1  sy: 1 0 0 0 0  lx: 6 5 5 3 5  ly: 3 0 0 0 3  match:3 2 1 -1 0 --change sy lx[4] + ly[0]=8
sx: 0 0 0 1 1  sy: 1 0 0 0 0  lx: 6 5 5 3 5  ly: 3 0 0 0 3  match:3 2 1 -1 0 --change sx u=3 --UpSearch
sx: 0 0 0 1 1  sy: 1 1 0 0 0  lx: 6 5 5 3 5  ly: 3 0 0 0 3  match:3 2 1 -1 0 --change sy lx[3] + ly[1]=3
sx: 0 0 1 1 1  sy: 1 1 0 0 0  lx: 6 5 5 3 5  ly: 3 0 0 0 3  match:3 2 1 -1 0 --change sx u=2 --UpSearch
sx: 0 0 1 1 1  sy: 1 1 1 0 0  lx: 6 5 5 3 5  ly: 3 0 0 0 3  match:3 2 1 -1 0 --change sy lx[4] + ly[2]=5
sx: 0 1 1 1 1  sy: 1 1 1 0 0  lx: 6 5 5 3 5  ly: 3 0 0 0 3  match:3 2 1 -1 0 --change sx u=1 --UpSearch
sx: 0 1 1 1 1  sy: 1 1 1 0 1  lx: 6 5 5 3 5  ly: 3 0 0 0 3  match:3 2 1 -1 0 --change sy lx[1] + ly[4]=8
sx: 1 1 1 1 1  sy: 1 1 1 0 1  lx: 6 5 5 3 5  ly: 3 0 0 0 3  match:3 2 1 -1 0 --change sx u=0 --UpSearch
sx: 1 1 1 1 1  sy: 1 1 1 0 1  lx: 6 5 5 3 5  ly: 3 0 0 0 3  match:3 2 1 -1 0 --After dfs isfind=0
sx: 1 1 1 1 1  sy: 1 1 1 0 1  lx: 5 4 4 2 4  ly: 4 1 1 0 4  match:3 2 1 -1 0 -- change lx and ly, dx = 1
sx: 0 0 0 0 1  sy: 0 0 0 0 0  lx: 5 4 4 2 4  ly: 4 1 1 0 4  match:3 2 1 -1 0 --change sx u=4
sx: 0 0 0 0 1  sy: 1 0 0 0 0  lx: 5 4 4 2 4  ly: 4 1 1 0 4  match:3 2 1 -1 0 --change sy lx[4] + ly[0]=8
sx: 0 0 0 1 1  sy: 1 0 0 0 0  lx: 5 4 4 2 4  ly: 4 1 1 0 4  match:3 2 1 -1 0 --change sx u=3 --UpSearch
sx: 0 0 0 1 1  sy: 1 1 0 0 0  lx: 5 4 4 2 4  ly: 4 1 1 0 4  match:3 2 1 -1 0 --change sy lx[3] + ly[1]=3
sx: 0 0 1 1 1  sy: 1 1 0 0 0  lx: 5 4 4 2 4  ly: 4 1 1 0 4  match:3 2 1 -1 0 --change sx u=2 --UpSearch
sx: 0 0 1 1 1  sy: 1 1 0 1 0  lx: 5 4 4 2 4  ly: 4 1 1 0 4  match:3 2 1 -1 0 --change sy lx[2] + ly[3]=4
sx: 0 0 1 1 1  sy: 1 1 0 1 0  lx: 5 4 4 2 4  ly: 4 1 1 0 4  match:3 2 1 2 0 --change match
sx: 0 0 1 1 1  sy: 1 1 0 1 0  lx: 5 4 4 2 4  ly: 4 1 1 0 4  match:3 3 1 2 0 --change match
sx: 0 0 1 1 1  sy: 1 1 0 1 0  lx: 5 4 4 2 4  ly: 4 1 1 0 4  match:4 3 1 2 0 --change match
sx: 0 0 1 1 1  sy: 1 1 0 1 0  lx: 5 4 4 2 4  ly: 4 1 1 0 4  match:4 3 1 2 0 --After dfs isfind=1
29
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值