用Octave计算指派问题(利润矩阵转成本矩阵)
广告
{{v.name}}
关键补充1:最大化利润问题的转化
若目标为总利润最大,需将利润矩阵\(P\)转化为等价的成本矩阵\(C\),转化方法为:
\(C = M - P\)
其中\(M\)为利润矩阵中的最大元素,转化后对\(C\)用匈牙利法求解最小成本,对应的指派方案即为原利润矩阵的最大利润方案。
关键补充2:非平衡指派问题的转化
若执行者数m≠任务数n(非平衡),需通过补0行/补0列将矩阵转化为max(m,n)阶方阵,补0的含义为:
- 若\(m< n\)(执行者少,任务多):补\(n-m\)行0元素,代表虚拟执行者,完成任务的成本为0(实际为未分配的任务,需根据场景调整);
- 若\(m>n\)(执行者多,任务少):补\(m-n\)列0元素,代表虚拟任务,执行者完成虚拟任务的成本为0(实际为闲置的执行者)。
关键补充3:退化问题
若行/列消去后,某行/列出现多个0元素,但无法选出n个独立0元素,属于退化问题,按步骤3-4正常调整矩阵即可,无需额外处理。
例子:
3个销售员(\(A_1,A_2,A_3\))分配3个区域(\(B_1,B_2,B_3\)),单位利润矩阵如下(单位:千元),求总利润最大的指派方案。
\(
P = \begin{bmatrix}
5 & 3 & 4 \\
4 & 6 & 2 \\
3 & 5 & 7
\end{bmatrix}
\)
步骤1:利润矩阵转成本矩阵
利润矩阵中最大元素\(M=7\),成本矩阵\(C=7-P\):
\(C = \begin{bmatrix}2&4&3\\3&1&5\\4&2&0\end{bmatrix}\)
步骤2:对成本矩阵执行匈牙利法
1. 行消去:每行减该行最小值
- 行1:2→[0,2,1]
- 行2:1→[2,0,4]
- 行3:0→[4,2,0]
行消去后矩阵:\(\begin{bmatrix}0&2&1\\2&0&4\\4&2&0\end{bmatrix}\)
2. 列消去:每列最小值均为0,无需消去,矩阵不变。
步骤2:寻找独立0元素
圈出3个独立0(\(A_1→B_1\)、\(A_2→B_2\)、\(A_3→B_3\)),为最优解。
步骤3:还原最大利润方案
对应原利润矩阵的指派方案:
- \(A_1→B_1\)(5)、\(A_2→B_2\)(6)、\(A_3→B_3\)(7);
总最大利润:\(5+6+7=18\)千元。
代码如下:
function [assign, opt_val] = hungarian_assign(C, type)
% 匈牙利法求解指派问题(n阶方阵)
% 输入:
% C: n×n矩阵,成本矩阵(type='min')或利润矩阵(type='max')
% type: 求解类型,'min'=最小化成本,'max'=最大化利润
% 输出:
% assign: 1×n向量,assign(i)=j表示将第i个执行者指派给第j个任务
% opt_val: 最优值(最小成本/最大利润)
% 输入校验
[n, m] = size(C);
if n ~= m
error('成本/利润矩阵必须为n阶方阵!');
end
if ~ismember(type, {'min', 'max'})
error('type必须为''min''或''max''!');
end
% 保存原始矩阵,用于最终计算最优值
origC = C;
% 最大化利润转化为最小化成本(用cost变量,不覆盖origC)
if strcmp(type, 'max')
M = max(origC(:));
cost = M - origC;
else
cost = origC;
end
% 匈牙利法核心步骤(行消去+列消去+找独立0+调整矩阵)
row_mask = false(1, n);
col_mask = false(1, n);
assign = zeros(1, n);
row_assign = zeros(1, n); % row_assign(j)=i 列j指派给行i
% 行消去
for i = 1:n
cost(i,:) = cost(i,:) - min(cost(i,:));
end
% 列消去
for j = 1:n
cost(:,j) = cost(:,j) - min(cost(:,j));
end
% 寻找独立0元素并重复调整(核心循环)
while true
% 标记独立0并获得列->行的指派(row_assign)
[row_mask, col_mask, row_assign] = mark_zero(cost, n);
% 画盖0线:线穿过所有未标记的行和所有已标记的列
line_num = (n - sum(row_mask)) + sum(col_mask);
if line_num == n
break;
end
% 调整矩阵:找到未被任何线覆盖的最小值并调整
% 未被覆盖的元素位于被标记的行且列未被标记
uncovered = row_mask' & ~col_mask;
vals = cost(uncovered);
if isempty(vals)
break;
end
theta = min(vals);
cost(uncovered) = cost(uncovered) - theta;
% 将theta加到被两条线覆盖的元素(即未被标记的行与已标记的列的交点)
cost(~row_mask', col_mask) = cost(~row_mask', col_mask) + theta;
end
% 确定指派方案
for j = 1:n
assign(row_assign(j)) = j;
end
% 计算最优值(直接使用原始矩阵origC)
opt_val = sum(origC(sub2ind([n,n], 1:n, assign)));
end
% 辅助函数:标记独立0元素
function [row_mask, col_mask, row_assign] = mark_zero(cost, n)
% 使用标准匈牙利流程:先贪婪找独立0(starred zeros),然后标记行/列
row_star = zeros(1, n); % row_star(i) = j 表示行i的star列
col_star = zeros(1, n); % col_star(j) = i 表示列j的star行
for i = 1:n
for j = 1:n
if cost(i,j) == 0 && row_star(i) == 0 && col_star(j) == 0
row_star(i) = j;
col_star(j) = i;
end
end
end
row_assign = col_star; % 返回列->行的指派(col j 指派给 row col_star(j))
% 标记:先标记所有没有star的行
row_mask = false(1, n);
col_mask = false(1, n);
for i = 1:n
if row_star(i) == 0
row_mask(i) = true;
end
end
% 反复:在被标记的行中标记含0的列;在被标记的列中标记含star的行,直到不再改变
while true
changed = false;
for i = 1:n
if row_mask(i)
for j = 1:n
if cost(i,j) == 0 && ~col_mask(j)
col_mask(j) = true;
changed = true;
end
end
end
end
for j = 1:n
if col_mask(j)
if col_star(j) ~= 0 && ~row_mask(col_star(j))
row_mask(col_star(j)) = true;
changed = true;
end
end
end
if ~changed
break;
end
end
end
结果如下:
>> [assign1, val1] = hungarian_assign([5 3 4;4 6 2;3 5 7], 'max')
assign1 =
1 2 3
val1 = 18