傻瓜都能明白的匈牙利KM算法

让我们详细解释匈牙利算法的工作原理,并通过一个简单的例子来说明正确的步骤,以便更清晰地理解。

我的理解与匈牙利算法的区别

我提出的理解如下:

1.	从未匹配的节点开始,选择第一个工人,匹配成本最小的任务。
2.	遍历所有工人,如果某个任务被多个工人匹配,保留成本最小的工人,其他工人选择下一个成本最小的任务。
3.	直到所有工人都有分配任务。

这种方法类似于贪心算法,即每一步都选择当前最优解。然而,贪心算法并不总能找到全局最优解,尤其是在有冲突时可能导致次优的总成本。

匈牙利算法则是一种系统化的方法,能够确保找到全局最优的任务分配方案。它通过矩阵变换和增广路径的方式,逐步优化分配,避免了贪心算法可能出现的局部最优问题。

匈牙利算法的基本步骤

匈牙利算法主要用于解决二分图的完美匹配问题,特别是分配问题,即将n个工人分配给n个任务,以最小化总成本。以下是其基本步骤:

1.	创建成本矩阵:构建一个n×n的矩阵,其中每个元素表示将第i个工人分配给第j个任务的成本。
2.	行缩减:
•	对每一行,找到最小的元素,并从该行的每个元素中减去这个最小值。
•	结果是每行至少有一个零。
3.	列缩减:
•	对每一列,找到最小的元素,并从该列的每个元素中减去这个最小值。
•	结果是每列至少有一个零。
4.	覆盖所有零:
•	使用最少数量的水平线和垂直线覆盖所有的零。
•	如果覆盖的线数等于n,找到最优匹配,算法结束。
•	否则,进行下一步。
5.	调整矩阵:
•	找到未被覆盖的最小元素。
•	从所有未被覆盖的元素中减去这个最小值。
•	将所有被两条线交叉覆盖的元素加上这个最小值。
•	返回步骤4。
6.	重复步骤4和5,直到找到最优匹配。

简单示例:

假设有3个工人(A, B, C)和3个任务(X, Y, Z),成本矩阵如下:

X	Y	Z

A 4 1 3
B 2 0 5
C 3 2 2

步骤1:创建成本矩阵

初始成本矩阵:

[ [4, 1, 3],
[2, 0, 5],
[3, 2, 2] ]

步骤2:行缩减

•	工人A的最小成本是1,减去1:

[4-1, 1-1, 3-1] => [3, 0, 2]

•	工人B的最小成本是0,减去0:

[2-0, 0-0, 5-0] => [2, 0, 5]

•	工人C的最小成本是2,减去2:

[3-2, 2-2, 2-2] => [1, 0, 0]

行缩减后的矩阵:

[ [3, 0, 2],
[2, 0, 5],
[1, 0, 0] ]

步骤3:列缩减

•	任务X的最小成本是1,减去1:

[3-1, 0, 2] => [2, 0, 2]
[2-1, 0, 5] => [1, 0, 5]
[1-1, 0, 0] => [0, 0, 0]

•	任务Y的最小成本是0,减去0:

[2, 0, 2]
[1, 0, 5]
[0, 0, 0]

•	任务Z的最小成本是0,减去0:

[2, 0, 2]
[1, 0, 5]
[0, 0, 0]

列缩减后的矩阵:

[ [2, 0, 2],
[1, 0, 5],
[0, 0, 0] ]

步骤4:覆盖所有零

使用最少数量的线覆盖所有零。这里有多个可能的覆盖方式,最少需要2条线(例如,一条水平线覆盖第三行,和一条垂直线覆盖第二列)。

因为覆盖线数(2)小于n(3),继续进行步骤5。

步骤5:调整矩阵

•	找到未被覆盖的最小元素。未被覆盖的元素为:
•	在第一行,第一列和第三列:2, 2
•	在第二行,第一列和第三列:1, 5
•	最小未覆盖元素是1。
•	从所有未覆盖的元素中减去1:

[2-1, 0, 2-1] => [1, 0, 1]
[1-1, 0, 5-1] => [0, 0, 4]
[0, 0, 0]

•	将所有被两条线交叉覆盖的元素加上1:
•	被水平线(第三行)和垂直线(第二列)交叉覆盖的元素是feature_map[2][1] = 0,加1后为1。

调整后的矩阵:

[ [1, 0, 1],
[0, 0, 4],
[0, 1, 0] ]

再次执行步骤4:覆盖所有零

使用最少数量的线覆盖所有零。这里需要3条线(例如,第一行、第二行、第三列),等于n(3),所以找到最优匹配,算法结束。

步骤6:找到最优匹配

通过标记矩阵中的零,可以找到每个工人的最佳任务分配:

•	工人A可以分配任务Y(唯一的零)。
•	工人B可以分配任务X或Y,但Y已被A占用,因此分配任务X。
•	工人C可以分配任务Z(唯一的零)。

最终分配:

•	A → Y,成本为1
•	B → X,成本为2
•	C → Z,成本为2

总成本为1 + 2 + 2 = 5,这是最小的总成本。

总结

我之前的理解中,尝试通过逐步匹配最小成本任务来解决分配问题,这种方法类似于贪心算法。然而,贪心算法可能在局部最优的选择下导致全局次优的结果。而匈牙利算法通过行和列的缩减、覆盖零、调整矩阵等步骤,系统地找到全局最优的任务分配方案。

通过上述简单的例子,可以看到匈牙利算法如何确保找到最小总成本的分配方案,而不是仅仅依靠每一步的局部最优选择。

-------------------更新一个扩展用法---------------------------

当然,我们可以探讨如何使用匈牙利(Hungarian)算法来寻找权重最大的匹配。这实际上是一个最大权重匹配(Maximum Weight Matching)的问题,与之前讨论的最小成本匹配类似,但目标相反:最大化总权重而不是最小化总成本。

最大权重匹配简介

  1. 什么是最大权重匹配?

最大权重匹配旨在为二分图中的每对工人和任务分配匹配,使得分配的总权重(或利润)最大化。每个工人只能被分配给一个任务,每个任务也只能被分配给一个工人。

  1. 应用场景

    • 资源分配:将资源分配给任务以最大化效益。
    • 人员安排:将员工分配到项目上,以最大化整体生产力。
    • 项目选择:选择项目组合以实现最大的总体收益。

将最大权重匹配转换为最小成本匹配

匈牙利算法原本设计用于解决最小成本匹配问题,但我们可以通过简单的转换将其用于最大权重匹配。具体方法如下:

1.	转换权重为成本:
•	找到权重矩阵中的最大值 Wmax。
•	构建一个新的成本矩阵 C,其中每个元素 C~i~ ~j~。
•	这样,原来的最大权重对应最小成本,确保匈牙利算法能找到最大权重的匹配。
2.	应用匈牙利算法:
•	使用转换后的成本矩阵运行匈牙利算法,找到最小成本的匹配。
•	该匹配对应于原权重矩阵中的最大权重匹配。

为什么这样转换有效?

通过将权重转换为成本,原本想要最大化的权重问题转化为最小化成本问题。因为较高的权重对应较低的成本,匈牙利算法在寻找最小成本匹配时,自然倾向于选择权重较高的匹配,从而实现了最大权重匹配的目标。

具体示例

让我们通过一个具体的示例,详细演示如何使用匈牙利算法找到最大权重匹配。

示例描述

假设我们有4个工人(Worker 1, Worker 2, Worker 3, Worker 4)和4个任务(Task A, Task B, Task C, Task D)。我们有以下权重矩阵,其中每个元素 Wi j 表示将第 i个工人分配给第 j 个任务的权重(例如,代表效益或利润):

[
[0.9 0.6 0 0]
[0 0.3 0.9 0]
[0.5 0.9 0 0]
[0 0 0.2 0]
]

步骤1:确定权重矩阵

首先,我们明确初始的权重矩阵:

W = [
[0.9 0.6 0 0]
[0 0.3 0.9 0]
[0.5 0.9 0 0]
[0 0 0.2 0]
]

步骤2:转换权重为成本

为了将最大权重匹配问题转换为最小成本匹配问题,我们需要找到权重矩阵中的最大值 Wmax
计算 Wmax:= 0.9
构建成本矩阵 Ci j=Wmax - Wi j

计算每个元素:
转换后的成本矩阵:
W = [
[0.0 0.3 0.9 0.9]
[0.0 0.6 0.0 0.9]
[0.4 0.0 0.9 0.9]
[0.0 0.9 0.7 0.9]
]
步骤3:应用匈牙利算法

现在,我们使用转换后的成本矩阵  来应用匈牙利算法,寻找最小成本的匹配,对应于原权重矩阵中的最大权重匹配。

步骤3.1:行缩减(Row Reduction)

对于每一行,找到最小的元素,并从该行的每个元素中减去这个最小值。

•	行1(Worker 1): 
•	最小值:0
•	缩减后:
•	行2(Worker 2): 
•	最小值:0
•	缩减后:
•	行3(Worker 3): 
•	最小值:0
•	缩减后:
•	行4(Worker 4): 
•	最小值:0.7
•	缩减后:

行缩减后的矩阵:

步骤3.2:列缩减(Column Reduction)

对于每一列,找到最小的元素,并从该列的每个元素中减去这个最小值。

•	列1(Task A): 
•	最小值:0
•	缩减后:
•	列2(Task B): 
•	最小值:0
•	缩减后:
•	列3(Task C): 
•	最小值:0
•	缩减后:
•	列4(Task D): 
•	最小值:0.2
•	缩减后:

列缩减后的矩阵:

[
\mathbf{C}_{\text{row & column reduced}} = \begin{bmatrix}
0 & 0.3 & 0.9 & 0.7 \
0.9 & 0.6 & 0 & 0.7 \
0.4 & 0 & 0.9 & 0.7 \
0.2 & 0.2 & 0 & 0 \
\end{bmatrix}
]

步骤3.3:覆盖所有零(Cover All Zeros)

我们需要使用最少数量的水平线(行)和垂直线(列)来覆盖矩阵中的所有零。目标是尽量少的线来覆盖所有的零。

当前矩阵中的零位置:

标记出所有的零位置:

•	(1,1)
•	(2,3)
•	(3,2)
•	(4,3), (4,4)

选择覆盖线的策略:

1.	优先选择包含最多零的行或列。
•	行1:1个零
•	行2:1个零
•	行3:1个零
•	行4:2个零
•	列1:1个零
•	列2:1个零
•	列3:2个零
•	列4:1个零
2.	选择覆盖零最多的行或列。
•	选择行4,覆盖两个零:(4,3) 和 (4,4)

覆盖线数:1(水平线覆盖行4)
3. 更新零位置(移除已被覆盖的零)。
• (4,3) 和 (4,4) 已被覆盖
• 余下的零位置:
• (1,1), (2,3), (3,2)
4. 再次选择覆盖零最多的行或列。
• 行1:1个零
• 行2:1个零
• 行3:1个零
• 列1:1个零
• 列2:1个零
• 列3:1个零
选择任意一条零元素的行或列。例如,选择列1,覆盖(1,1)
覆盖线数:2(水平线覆盖行4,垂直线覆盖列1)
5. 更新零位置(移除已被覆盖的零)。
• (1,1) 被覆盖
• 余下的零位置:
• (2,3), (3,2)
6. 再次选择覆盖零最多的行或列。
• 行2:1个零
• 行3:1个零
• 列2:1个零
• 列3:1个零
选择列3,覆盖(2,3)
覆盖线数:3(水平线覆盖行4,垂直线覆盖列1和列3)
7. 更新零位置(移除已被覆盖的零)。
• (2,3) 被覆盖
• 余下的零位置:
• (3,2)
8. 选择覆盖零最多的行或列。
• 行3:1个零
• 列2:1个零
选择列2,覆盖(3,2)
覆盖线数:4(水平线覆盖行4,垂直线覆盖列1、列3和列2)
9. 剩余未被覆盖的零:无

总结:

我们使用了4条线(1条水平线覆盖行4,3条垂直线覆盖列1、列3和列2)来覆盖所有的零。这等于矩阵的维度 ,因此,我们已经找到最优匹配,算法可以结束。

步骤3.4:找到最优匹配

既然覆盖线的数量等于矩阵的维度,我们可以通过选择不冲突的零来找到最优匹配。

选择不冲突的零:

1.	工人1:
•	零位置:(1,1)
•	分配:Worker 1 → Task A,成本 = 0
2.	工人2:
•	零位置:(2,3)
•	分配:Worker 2 → Task C,成本 = 0
3.	工人3:
•	零位置:(3,2)
•	分配:Worker 3 → Task B,成本 = 0
4.	工人4:
•	零位置:(4,3), (4,4)
•	由于Task C已被Worker 2占用,选择Task D
•	分配:Worker 4 → Task D,成本 = 0

最终分配:

•	Worker 1 → Task A,成本 = 0
•	Worker 2 → Task C,成本 = 0
•	Worker 3 → Task B,成本 = 0
•	Worker 4 → Task D,成本 = 0

总成本:0 + 0 + 0 + 0 = 0

注意:在此示例中,由于多个零元素存在,存在多种最优分配方案。例如:

•	方案1:
•	Worker 1 → Task A
•	Worker 2 → Task C
•	Worker 3 → Task B
•	Worker 4 → Task D
•	方案2:
•	Worker 1 → Task A
•	Worker 2 → Task C
•	Worker 3 → Task B
•	Worker 4 → Task D

两种方案的总成本均为0,都是最优的匹配方案。

完整匹配表

工人 任务 权重 成本
Worker 1 Task A 0.9 0
Worker 2 Task C 0.9 0
Worker 3 Task B 0.9 0
Worker 4 Task D 0 0

总成本:0

对应的总权重:0.9 + 0.9 + 0.9 + 0 = 2.7

总结

通过将最大权重匹配问题转换为最小成本匹配问题,我们成功应用了匈牙利算法来找到最优的任务分配方案,使得总权重最大化。关键步骤包括:

1.	权重转换:将权重矩阵转换为成本矩阵。
2.	应用匈牙利算法:执行行缩减、列缩减、覆盖零、调整矩阵等步骤。
3.	确定匹配:选择不冲突的零,确保每个工人分配一个唯一的任务。

重要提醒

•	矩阵必须是方阵:如果工人和任务数量不相等,可以通过添加虚拟工人或任务,使得矩阵成为方阵。
•	处理负权重:如果权重矩阵中存在负值,可以通过适当的转换(例如,加上一个常数)确保所有权重为正值,再进行转换。
•	多种最优匹配:在权重矩阵中存在多个相同权重的情况下,可能存在多种最优分配方案。

扩展阅读

•	scipy.optimize.linear_sum_assignment:Python中scipy库提供了linear_sum_assignment函数,可以方便地实现匈牙利算法。

import numpy as np
from scipy.optimize import linear_sum_assignment

原始权重矩阵

W = np.array([
[0.9, 0.6, 0, 0],
[0, 0.3, 0.9, 0],
[0.5, 0.9, 0, 0],
[0, 0, 0.2, 0]
])

转换为成本矩阵

W_max = W.max()
C = W_max - W

应用匈牙利算法

row_ind, col_ind = linear_sum_assignment©

输出匹配结果

for i, j in zip(row_ind, col_ind):
print(f"Worker {i+1} -> Task {chr(65+j)} | Weight: {W[i, j]} | Cost: {C[i, j]}")

输出示例:

Worker 1 -> Task A | Weight: 0.9 | Cost: 0.0
Worker 2 -> Task C | Weight: 0.9 | Cost: 0.0
Worker 3 -> Task B | Weight: 0.9 | Cost: 0.0
Worker 4 -> Task D | Weight: 0.0 | Cost: 0.9

•	处理不等数量的工人和任务:在工人和任务数量不相等时,可以通过添加虚拟工人或任务,将矩阵扩展为方阵,并赋予虚拟任务或工人一个适当的权重(如0)来进行匹配。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

海尔辛

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值