在生活中经常遇到这样的问题,某单位需完成n项任务,恰好有n个人可承担这些任务。由于每人的专长不同,各人完成任务不同(或 所费时间),效率也不同。于是产生应指派哪个人去完成哪项任务 ,使完成n项任务的总效率最高(或所需总时间最小)。
• 这类问题称为指派问题或分派问题(assignment problem)。
• 例:有一份中文说明书,需译成英、日、德、俄5种文字。分 别记作A、B、C、D、E。现有甲、乙、丙、丁、戊5人。他们将中文说明书翻译成不同语种的说明书所需时间如下表所示。问应指派何人去完成何工作,使所需总时间最少?
• 对应每个指派问题,需有类似上表那样的数表,称为效率矩阵或系数矩阵,其元素 𝑐𝑖𝑗>0(𝑖,𝑗 = 1,2,…,𝑛)表示指 派第𝑖人去完成第𝑗项任务时的效率(或时间、成本等)。
指派问题的解法:
第一步:使指派问题的系数矩阵经变换,在各行各列中都出现0 元素。
• (1) 从系数矩阵的每行元素减去该行的最小元素;
• (2) 再从所得系数矩阵的每列元素中减去该列的最小元素。
• 若某行(列)已有0元素,那就不必再减了。
第二步:进行试指派,以寻求最优解。为此,按以下步骤进行。
• 经第一步变换后,系数矩阵中每行每列都已有了0元素;但需找 出𝑛个独立的0元素。若能找出,就以这些独立0元素对应解矩阵 (𝑥𝑖𝑗)中的元素为1,其余为0,这就得到最优解。
• 当𝑛较小时,可用观察法、试探法去找出𝑛个独立0元素。
• 若𝑛较大时,就必须按一定的步骤去找,常用的步骤为:
• (1) 从只有一个0元素的行(列)开始,给这个0元素加圈,记作◎ 。这表示对这行所代表的人,只有一种任务可指派。然后划去◎ 所在列(行)的其他0元素,记作Φ。这表示这列所代表的任务已指派完,不必再考虑别人了。
• (2) 给只有一个0元素列(行)的0元素加圈,记作◎;然后划去◎ 所在行的0元素,记作Φ。
• (3) 反复进行(1),(2)两步,直到所有0元素都被圈出和划掉为止。
• (4) 若仍有没有划圈的0元素,且同行(列)的0元素至少有两个( 表示对这个可以从两项任务中指派其一)。这可用不同的方案去 试探。 • 从剩有0元素最少的行(列)开始,比较这行各0元素所在列中0元 素的数目,选择0元素少的那列的这个0元素加圈(表示选择性多 的要“礼让”选择性少的)。然后划掉同行同列的其他0元素。可反复进行,直到所有0元素都已圈出和划掉为止。
• (5) 若◎元素的数目𝑚等于矩阵的阶数𝑛,那么这指派问题的最优解已得到。若𝑚<𝑛,则转入下一步。
第三步(𝑚 < 𝑛时的处理办法):作最少的直线覆盖所有0元素,以 确定该系数矩阵中能找到最多的独立元素数。为此按以下步骤进 行:
• (1) 对没有◎的行打√号;
• (2) 对已打√号的行中所有含Φ元素的列打√号;
• (3) 再对打有√号的列中含◎元素的行打√号;
• (4) 重复(2),(3)直到得不出新的打√号的行、列为止。
• (5) 对没有打√号的行画一横线,有打√号的列画一纵线,这就 得到覆盖所有0元素的最少直线数。
• 令这直线数为𝑙。若𝑙<𝑛,说明必须再变换当前的系数矩阵,才能找到𝑛个独立的0元素,为此需要转第四步:若l=n,而m<n, 应回到第二步(4),另行试探。
第四步:
• 对矩阵进行变换的目的是增加0元素。
• 为此,在没有被直线覆盖的部分中找出最小元素,然后在打√行 各元素中都减去这最小元素,而在打√列的各元素都加上这最小 元素,以保证原来0元素不变。
• 这样得到新系数矩阵(它的最优解和原问题相同)。若得到𝑛个独 立的0元素,则已得最优解,否则回到第三步重复进行。
#指派问题
import numpy as np
from collections import Counter
def getSimMat(effMat): #简化效率矩阵
for i in range(effMat.shape[0]):
if 0 in effMat[i]:
continue
else:
matList = effMat[i].tolist()
effMat[i] -= min(matList)
for i in range(effMat.shape[1]):
minVal = float("inf")
for j in range(effMat.shape[0]):
if effMat[j][i] < minVal:
minVal = effMat[j][i]
if minVal == 0:
break
for k in range(effMat.shape[0]):
effMat[k][i] -= minVal
def circleOneZero(effMat):
while 0 in effMat:
judge = False
for i in range(effMat.shape[0]):
zeroCount, zeroColumn = 0, -1
for j in range(effMat.shape[1]):
if effMat[i][j] == 0:
zeroCount += 1
zeroColumn = j
if zeroCount == 1:
effMat[i][zeroColumn] = -1
for k in range(effMat.shape[0]):
if k != i:
if effMat[k][zeroColumn] == 0:
effMat[k][zeroColumn] = -1000
judge = True
else:
continue
for i in range(effMat.shape[1]):
zeroCount, zeroRow = 0, -1
for j in range(effMat.shape[0]):
if effMat[j][i] == 0:
zeroCount += 1
zeroRow = j
if zeroCount == 1:
effMat[zeroRow][i] = -1
for k in range(len(effMat[zeroRow])):
if k != i:
if effMat[zeroRow][k] == 0:
effMat[zeroRow][k] = -1000
judge = True
else:
continue
if judge is False:
break
return judge
def circleTwoZero(effMat):
maxCount = float("inf")
leastZeroRow = -1
leastZeroColumn = -1
zeroColumnList = []
for i in range(effMat.shape[0]):
count = Counter(effMat[i])
zeroCount = count[0]
if zeroCount != 0:
if zeroCount < maxCount:
maxCount = zeroCount
leastZeroRow = i
for i in range(effMat.shape[1]):
if effMat[leastZeroRow][i] == 0:
zeroColumnList.append(i)
zeroCount = 0
maxCount = float("inf")
for j in zeroColumnList:
for i in range(effMat.shape[0]):
if effMat[i][j] == 0:
zeroCount += 1
if zeroCount < maxCount:
maxCount = zeroCount
leastZeroColumn = j
effMat[leastZeroRow][leastZeroColumn] = -1
for i in range(effMat.shape[1]):
if effMat[leastZeroRow][i] == 0:
effMat[leastZeroRow][i] = -1000
for i in range(effMat.shape[0]):
if effMat[i][leastZeroColumn] == 0:
effMat[i][leastZeroColumn] = -1000
def circle(effMat): #画圈
while 0 in effMat:
judge = circleOneZero(effMat)
if judge is False:
circleTwoZero(effMat)
else:
if np.sum(effMat == -1) == effMat.shape[0]:
pass
else:
mark(effMat)
def mark(effMat):
markRow = []
markColumn = []
for i in range(effMat.shape[0]):
if -1 not in effMat[i]:
markRow.append(i)
# if -1000 in effMat[i]:
# markColumn.append(effMat[i].tolist().index(-1000))
for markR in markRow:
for j in range(effMat.shape[1]):
if effMat[markR][j] == -1000:
markColumn.append(j)
for markC in markColumn:
for i in range(effMat.shape[0]):
if effMat[i][markC] == -1:
markRow.append(i)
l = effMat.shape[0] - len(markRow) + len(markColumn)
if l < effMat.shape[0]:
minVal = float("inf")
for i in range(effMat.shape[0]):
if i in markRow:
for j in range(effMat.shape[1]):
if j not in markColumn:
if effMat[i][j] < minVal:
minVal = effMat[i][j]
else:
continue
for mc in markRow:
effMat[mc] -= minVal
for j in range(effMat.shape[1]):
if j in markColumn:
for i in range(effMat.shape[0]):
effMat[i][j] += minVal
effMat[effMat == -1] = 0
effMat[effMat == -1000] = 0
judgeNew = circleOneZero(effMat)
circleTwoZero(effMat)
x = []
if 0 in effMat:
for effMati in effMat:
if 0 in effMati:
for i in range(len(effMati)):
if -1 in effMati:
if effMati[i] == 0:
effMati[i] = -1000
else:
if effMati[i] == 0:
for j in range(effMat.shape[0]):
x.append(effMat[j][i])
if -1 in x:
effMati[i] = -1000
else:
effMati[i] = -1
bol = effMat == -1
num = effMat[bol]
if num.size == effMat.shape[0]:
pass
else:
circle(effMat)
if __name__ == '__main__':
efficiencyMat = np.array([[12,7,9,7,9],[8,9,6,6,6],[7,17,12,14,9],[15,14,6,6,10],[4,10,7,10,9]])
getSimMat(efficiencyMat)
circle(efficiencyMat)
mark(efficiencyMat)
efficiencyMat[efficiencyMat != -1] = 0
efficiencyMat[efficiencyMat == -1] = 1
print(efficiencyMat)