匈牙利算法是为了求解二分图的最大匹配问题。
先介绍匈牙利算法,引用百度百科的说法,匈牙利算法是一种在多项式时间内求解任务分配问题的组合优化算法,并推动了后来的原始对偶方法。美国数学家哈罗德·库恩于1955年提出该算法。此算法之所以被称作匈牙利算法,是因为算法很大一部分是基于以前匈牙利数学家Dénes Kőnig和Jenő Egerváry的工作之上创建起来的。
匈牙利方法(或 Kuhn 算法)是由4个基本步骤组成的迭代过程。该方法使用“最小行集”覆盖“操纵”成本矩阵的零点,当所需的“最小行集”等于给定成本矩阵的维数时,过程终止。
Munkres 算法是一种最优分配算法,可以看作是匈牙利算法的一个变种。它的不同之处在于处理过程中:
该算法的一个重要特征是它的发明人在介绍该算法的论文中还导出了一个方程来计算“完全解决任何 N × N N\times N N×N指派问题所需的最大运算数”。该公式由以下公式给出:
N m a x = ( n / 6 ) ( 11 n 2 + 12 n + 31 ) N_{max}=(n/6)(11n^2+12n+31) Nmax=(n/6)(11n2+12n+31)
这比完全枚举方法所需的操作( ! n !n !n)小的多。
算法流程如下图所示:
以下6步算法是原始 Munkres 的分配算法(有时称为匈牙利算法)的改进形式。该算法描述了如何通过对 0 0 0 加星和加撇以及覆盖和揭开行和列来手动操作二维矩阵。这是因为,在出版时(1957年),很少有人可以使用计算机,并且算法是手工操作的。
步骤0:创建一个称为成本矩阵的 n × m n\times m n×m矩阵,其中每个元素代表将 n n n 个工人之一分配给 m m m 个工作之一的成本。旋转矩阵,使列至少与行一样多,并令 k = min ( n , m ) k=\min(n,m) k=min(n,m)。
步骤1:对于矩阵的每一行,找到最小的元素,并从该行的每个元素中减去它。转到步骤2。
步骤2:在结果矩阵中找到零( z z z)。如果其所在行和列中没有加星号的零,则将 z z z 标星。对矩阵中的每个元素重复此操作。转到步骤3。
步骤3:覆盖包含加星零的列。
步骤4:
步骤5:按以下步骤构造一系列交替的加撇和加星号的零。
步骤6:将在步骤4中找到的值加到每个覆盖行的每个元素,并从每个未覆盖列的每个元素中减去它。返回第4步,而不更改任何星标、撇号或覆盖线。
完成:分配对由成本矩阵中加星号的零的位置指示。如果 C ( i , j ) C(i,j) C(i,j) 是加星零,则将与行 i i i关联的元素分配给与列 j j j 关联的元素。
在第4步中,可能的情况是存在一个未覆盖且加撇的零,如果在其行中没有加星号的零,则程序进入步骤5;如果根本没有未覆盖的零,程序转到步骤6。 步骤5为增广路径算法,步骤6修改成本矩阵。
function [assignment,cost] = Hungarian(costMat)
% MUNKRES Munkres (Hungarian) Algorithm for Linear Assignment Problem.
%
% [ASSIGN,COST] = munkres(COSTMAT) returns the optimal column indices,
% ASSIGN assigned to each row and the minimum COST based on the assignment
% problem represented by the COSTMAT, where the (i,j)th element represents the cost to assign the jth
% job to the ith worker.
%
% Partial assignment: This code can identify a partial assignment is a full
% assignment is not feasible. For a partial assignment, there are some
% zero elements in the returning assignment vector, which indicate
% un-assigned tasks. The cost returned only contains the cost of partially
% assigned tasks.
% This is vectorized implementation of the algorithm. It is the fastest
% among all Matlab implementations of the algorithm.
% Examples
% Example 1: a 5 x 5 example
%{
[assignment,cost] = munkres(magic(5));
disp(assignment); % 3 2 1 5 4
disp(cost); %15
%}
% Example 2: 400 x 400 random data
%{
n=400;
A=rand(n);
tic
[a,b]=munkres(A);
toc % about 2 seconds
%}
% Example 3: rectangular assignment with inf costs
%{
A=rand(10,7);
A(A>0.7)=Inf;
[a,b]=munkres(A);
%}
% Example 4: an example of partial assignment
%{
A = [1 3 Inf; Inf Inf 5; Inf Inf 0.5];
[a,b]=munkres(A)
%}
% a = [1 0 3]
% b = 1.5
% Reference:
% "Munkres' Assignment Algorithm, Modified for Rectangular Matrices",
% http://csclab.murraystate.edu/bob.pilgrim/445/munkres.html
% version 2.3 by Yi Cao at Cranfield University on 11th September 2011
assignment = zeros(1,size(costMat,1));
cost = 0;
validMat = costMat == costMat & costMat < Inf;
bigM = 10^(ceil(log10(sum(costMat(validMat))))+1);
costMat(~validMat) = bigM;
% costMat(costMat~=costMat)=Inf;
% validMat = costMat0)
break
end
coverColumn = false(1,n);
coverColumn(starZ(starZ>0))=true;
coverRow = false(n,1);
primeZ = zeros(n,1);
[rIdx, cIdx] = find(dMat(~coverRow,~coverColumn)==bsxfun(@plus,minR(~coverRow),minC(~coverColumn)));
while 1
%**************************************************************************
% STEP 4: Find a noncovered zero and prime it. If there is no starred
% zero in the row containing this primed zero, Go to Step 5.
% Otherwise, cover this row and uncover the column containing
% the starred zero. Continue in this manner until there are no
% uncovered zeros left. Save the smallest uncovered value and
% Go to Step 6.
%**************************************************************************
cR = find(~coverRow);
cC = find(~coverColumn);
rIdx = cR(rIdx);
cIdx = cC(cIdx);
Step = 6;
while ~isempty(cIdx)
uZr = rIdx(1);
uZc = cIdx(1);
primeZ(uZr) = uZc;
stz = starZ(uZr);
if ~stz
Step = 5;
break;
end
coverRow(uZr) = true;
coverColumn(stz) = false;
z = rIdx==uZr;
rIdx(z) = [];
cIdx(z) = [];
cR = find(~coverRow);
z = dMat(~coverRow,stz) == minR(~coverRow) + minC(stz);
rIdx = [rIdx(:);cR(z)];
cIdx = [cIdx(:);stz(ones(sum(z),1))];
end
if Step == 6
% *************************************************************************
% STEP 6: Add the minimum uncovered value to every element of each covered
% row, and subtract it from every element of each uncovered column.
% Return to Step 4 without altering any stars, primes, or covered lines.
%**************************************************************************
[minval,rIdx,cIdx]=outerplus(dMat(~coverRow,~coverColumn),minR(~coverRow),minC(~coverColumn));
minC(~coverColumn) = minC(~coverColumn) + minval;
minR(coverRow) = minR(coverRow) - minval;
else
break
end
end
%**************************************************************************
% STEP 5:
% Construct a series of alternating primed and starred zeros as
% follows:
% Let Z0 represent the uncovered primed zero found in Step 4.
% Let Z1 denote the starred zero in the column of Z0 (if any).
% Let Z2 denote the primed zero in the row of Z1 (there will always
% be one). Continue until the series terminates at a primed zero
% that has no starred zero in its column. Unstar each starred
% zero of the series, star each primed zero of the series, erase
% all primes and uncover every line in the matrix. Return to Step 3.
%**************************************************************************
rowZ1 = find(starZ==uZc);
starZ(uZr)=uZc;
while rowZ1>0
starZ(rowZ1)=0;
uZc = primeZ(rowZ1);
uZr = rowZ1;
rowZ1 = find(starZ==uZc);
starZ(uZr)=uZc;
end
end
% Cost of assignment
rowIdx = find(validRow);
colIdx = find(validCol);
starZ = starZ(1:nRows);
vIdx = starZ <= nCols;
assignment(rowIdx(vIdx)) = colIdx(starZ(vIdx));
pass = assignment(assignment>0);
pass(~diag(validMat(assignment>0,pass))) = 0;
assignment(assignment>0) = pass;
cost = trace(costMat(assignment>0,assignment(assignment>0)));
function [minval,rIdx,cIdx]=outerplus(M,x,y)
ny=size(M,2);
minval=inf;
for c=1:ny
M(:,c)=M(:,c)-(x+y(c));
minval = min(minval,min(M(:,c)));
end
[rIdx,cIdx]=find(M==minval);