求解分配问题(一) Kuhn-Munkre算法

本文详细介绍了Kuhn-Munkres算法,用于解决分配问题,如最短开发时间分配。通过行和列减法操作,寻找最优解。文章包括算法流程、划线方法、非平衡处理及Python代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

分配问题(Assignment Problem)很常见,例如给多个开发者安排开发任务,一个开发者只能分配到一个任务,一个任务也只能由一个人完成,不同的人对不同的任务有不同的完成时间,那么如何进行安排使得总的开发时间最短就是一个典型的分配问题。如果说传输问题是一类特殊的线性规划问题(传输问题定义),那么分配问题则是一类特殊的传输问题,如果我们把传输问题中所有Supplier和Demander的供应量或需求量都降到1,那么这就变成了一个分配问题

既然分配问题属于传输问题,那么Stepping-Stone算法或者Modified Distribution算法当然也可以直接应用于分配问题,但是我们可以想象一下,对于一个 n × n n\times n n×n的分配问题,它任何一个可行解的矩阵形式都是只有n个1,而迭代中非退化的条件是1的数量等于 n × n − 1 n\times n-1 n×n1,这意味着每次迭代计算都要处理退化情况,所以直接应用传输问题的算法效率非常低。文本介绍处理分配问题常用的一种算法,一般称之为匈牙利算法(Hungarian Algorithm),不过实际上匈牙利算法的原始版本并非处理传输问题,下面介绍的其实是Harold W.Kuhn和James Munkre在此基础上提出的,所以严格说应该叫Kuhn-Munkre算法

算法主流程

先举个案例来方便描述:我们就假设有A,B,C,D四个开发任务,1,2,3,4四个开发者,各个开发者对开发任务的评估时间(单位: 分钟)可以用下面的cost矩阵来描述

Task \ Person 1 2 3 4
A 210 90 180 160
B 100 70 130 200
C 175 105 140 170
D 80 65 105 120

分配(A,1),(B,2),(C,3),(D,4)就是一种可行解,表述成矩阵形式:

Assign 1 2 3 4
A 1 0 0 0
B 0 1 0 0
C 0 0 1 0
D 0 0 0 1

对于上面的cost矩阵,我们通过观察可以发现,对某一个行或者某一列统一减去某个值(例如下表中将第一行统一减去10),其实并不影响问题的描述,也就是说通过行或列的减值操作,得到新的cost矩阵,在它上面计算的结果和原始的cost矩阵是一样的

Task \ Person 1 2 3 4
A 210-10=200 90-10=80 180-10=170 160-10=150
B 100 70 130 200
C 175 105 140 170
D 80 65 105 120

再考虑,如果某个开发者对一个task的评估时间是零,那么我们当然优先给他分配这个任务;如果每个开发者都有一个互不相同的评估时间是零的task,那么这种分配就是最优的,因为总时间是零。
结合这两个特点,我们可以想到,对各行各列,都减去那一行或列的最小值,这样就可以转化到一个包含多个零的cost矩阵,如果这个cost矩阵中存在没有行列冲突的4个零,那么我们就已经找到了最优解,例如下面的形式,分配(A,1),(C,2),(B,3),(D,4)就是最优分配

Task \ Person 1 2 3 4
A 0 80 170 150
B 100 70 0 200
C 175 0 140 170
D 80 65 105 0

所以算法的第一步就是进行行列的减法操作,先将每一行减去该行的最小值,再将每一列减去该列的最小值,这样就可以产生多个零:

行减(Row Reduction):

Task \ Person 1 2 3 4
A 120 0 90 70
B 30 0 60 130
C 70 0 35 65
D 15 0 40 55

列减(Column Reduction):

Task \ Person 1 2 3 4
A 105 0 55 15
B 15 0 25 75
C 55 0 0 10
D 0 0 5 0

现在我们需要评估一下当前的cost矩阵,是否存在互相独立的4个零,所谓互相独立,指它们中任意两个既不在同一行也不在同一列;如何评估呢,我们在cost矩阵上划线,可以是在行上也可以在列上,用最少的线来覆盖到所有的零,如果刚好是4条,那么就存在相互独立的4个零;如果小于4条,则不存在,这是一个可以数学证明的推论,我们直接接受这个设定。在上面这个cost矩阵中,我们发现只用3条线就可以覆盖到所有的零

在这里插入图片描述
这也就意味着,我们找不到相互独立的四个零,当前不存在一个cost为零的最优解;怎么办呢,一个自然的想法就是在划线外的区域产生更多的零,这样划出的线可能达到4条了;那么怎么只对划线外的单元格进行减值操作呢,注意行减或列减操作必须是整行或整列,不能对单个单元格,但是我们可以组合行减和列减达到目的:先对所有非划线行的单元进行减值操作:
在这里插入图片描述
然后对所有在划线列上的单元进行加值操作:
在这里插入图片描述
这样的最终效果就是对所有划线外的单元减去 h h h,对划线的交点单元加上 h h h,而这个 h h h我们可以取划线外单元格的最小值,即10,所以,减值的结果变成:

Task \ Person 1 2 3 4
A 95 0 55 5
B 5 0 25 65
C 45 0 0 0
D 0 10 15 0

再来评估,发现仍然只能用3条线来覆盖,我们还没找到最优解:
在这里插入图片描述
继续对矩阵进行减值操作,得到:

Task \ Person 1 2 3 4
A 90 0 50 0
B 0 0 20 60
C 45 5 0 0
D 0 15 15 0

现在我们发现已经存在四个独立的零了:(B,1),(A,2),(C,3),(D,4),这就是最优的分配方案在这里插入图片描述
因此,Kuhn-Munkre算法的步骤就是上面描述的过程:

  1. Row Reduction: 对cost矩阵的每一行,减去该行的最小值
  2. Column Reduction: 对每一列,减去该列的最小值
  3. 评估: 在cost矩阵上找出能覆盖所有零的最少数量的划线,如果划线数等于cost矩阵的边长n,那么已经存在最佳分配,退出算法;如果小于n,则继续4
  4. h h h是所有不在划线中的单元的最小值,将所有不在划线中的单元减去 h h h,对划线的交点单元则加上 h h h
  5. 重复3,4

你可能会对步骤4有疑问,会不会步骤4一直产生不了最优解,导致算法陷入死循环呢? 其实步骤4的计算方式决定了算法一定会在有限步数内终止,证明如下:

  • α \alpha α是步骤4中的减数,集合 I I I是行覆盖线集, J J J是列覆盖线集,在步骤4中,我们是先从 n ( n − ∣ I ∣ ) n(n-|I|) n(nI)个单元中减 α \alpha α,然后给 n ∣ J ∣ n|J| nJ个单元加上 α \alpha α,所以总的变化量是 α n ∣ J ∣ − α n ( n − ∣ I ∣ ) = α n ( ∣ I ∣ + ∣ J ∣ − n ) \alpha n|J|-\alpha n(n-|I|)=\alpha n(|I|+|J|-n) αn
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值