GNN全局最近邻算法匈牙利算法 或 Munkres分配算法的MATLAB实现

Munkres 分配算法

匈牙利算法是为了求解二分图的最大匹配问题

先介绍匈牙利算法,引用百度百科的说法,匈牙利算法是一种在多项式时间内求解任务分配问题的组合优化算法,并推动了后来的原始对偶方法。美国数学家哈罗德·库恩于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)小的多。

Munkres 算法

算法流程如下图所示:

no
yes
yes
no
yes
no
给定分配矩阵C
每行元素减去最小值得到第一版差额矩阵CR1
遍历CR1中的零, 如果其所在行和列中没有加星号的零则标星
覆盖包含加星零的列
总共覆盖了k列?
有未覆盖的零?
finish
将未覆盖的零加撇
保存最小的未覆盖值
令覆盖行的元素加上最小值而未覆盖列的每个元素减去它
所在行有加星的零?
覆盖该行并揭开包含加星号零的列
构造行列方向交替的加撇和加星的零序列
取消序列中零的星号,并将加撇零转为加星零,揭开矩阵中的每一行

以下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:覆盖包含加星零的列。

    • 如果总共覆盖了 k k k 列,则加星号的零表示完整的唯一分配集。在这种情况下,请转到“完成”;
    • 否则,请转到步骤4。
  • 步骤4:

    • 找到一个未覆盖的零并将其加撇。
    • 如果在包含该加撇零的行中没有加星号的零,请转到步骤5。
    • 否则,请覆盖该行并揭开包含加星号的零的列。
    • 以这种方式继续,直到没有剩余的零为止。
    • 保存最小的未覆盖值,然后转到步骤6。
  • 步骤5:按以下步骤构造一系列交替的加撇和加星号的零。

    • z 0 z_0 z0表示在步骤4中发现的未覆盖的加撇零。
    • z 1 z_1 z1表示 z 0 z_0 z0所在列中的加星的零(如果有)。
    • z 2 z_2 z2表示 z 1 z_1 z1所在行中的加撇零(总会有一个)。
    • 继续该序列直到加撇零所在列中没有加星的零时终止。
    • 取消序列中零的星号,并对加撇零加星,擦除所有撇号并揭开矩阵中的每一行。
    • 返回步骤3。
  • 步骤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 = costMat<Inf;
validCol = any(validMat,1);
validRow = any(validMat,2);

nRows = sum(validRow);
nCols = sum(validCol);
n = max(nRows,nCols);
if ~n
    return
end

maxv=10*max(costMat(validMat));

dMat = zeros(n) + maxv;
dMat(1:nRows,1:nCols) = costMat(validRow,validCol);

%*************************************************
% Munkres' Assignment Algorithm starts here
%*************************************************

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%   STEP 1: Subtract the row minimum from each row.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
minR = min(dMat,[],2);
minC = min(bsxfun(@minus, dMat, minR));

%**************************************************************************  
%   STEP 2: Find a zero of dMat. If there are no starred zeros in its
%           column or row start the zero. Repeat for each zero
%**************************************************************************
zP = dMat == bsxfun(@plus, minC, minR);

starZ = zeros(n,1);
while any(zP(:))
    [r,c]=find(zP,1);
    starZ(r)=c;
    zP(r,:)=false;
    zP(:,c)=false;
end

while 1
%**************************************************************************
%   STEP 3: Cover each column with a starred zero. If all the columns are
%           covered then the matching is maximum
%**************************************************************************
    if all(starZ>0)
        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);

参考

  1. Munkres’ Assignment Algorithm
  2. Munkres 分配算法
  3. hungarian-algorithm-cpp
  4. 匈牙利算法和Kuhn-Munkres算法
  5. 带你入门多目标跟踪(三)匈牙利算法&KM算法
  6. 趣写算法系列之–匈牙利算法
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值