算法描述
最大权值匹配算法(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