Juliaで実装するハンガリアン法

未分類
Table of Content

概要

  • N個のものをM個にそれぞれペアで関連づけるマッチングで,コストが最小になるように割り当てる
  • $O(N^3)$

アルゴリズム

  1. 行の最小値をその行から引く
    • すべての行について行う
  2. 列の最小値をその列から引く
    • すべての列について行う
  3. 0を各行, 各列から1つずつ選ぶことができるか判定 (行数個選択)
    • 選べた場合それが解
  4. 0を含む行または列を取り除く
    • 行数+列数が最小になるように選択する
    • 最小数個だけ取り除くには,最大個数の0を含む行か列を選択していく。
  5. 残りの要素の最小値$m$を各要素から引く
  6. 最小値$m$を取り除いた行, 列が交わる要素に加える
  7. 3に戻る

Juliaでの実装

"
`M`: cost matrix
"""
function hungarian(M)
    # 各行の最小値で引く
    M = mapslices(row->row .- minimum(row), M, dims=2)
    # 各列の最小値で引く
    M = mapslices(col->col.- minimum(col), M, dims=1)
    # 各行各列に0が含まれるかcheck
    while true
        pairs = getpairs(M)
        # ペアが見つかれば終了
        !isnothing(pairs) && return pairs
        # ペアがないので行と列を消す
        zerocoords = findall(x->iszero(x), M)
        rows, cols = Int[], Int[]
        while 0 < length(zerocoords)
            target = pop!(zerocoords)
            nzerocol = count(coord->coord[2] == target[2], zerocoords) # 列の0
            nzerorow = count(coord->coord[1] == target[1], zerocoords) # 行の0
            if nzerocol < nzerorow
                zerocoords = filter(coord->coord[1] != target[1], zerocoords) # 同一行を取り除く
                push!(rows, target[1])
            else
                zerocoords = filter(coord->coord[2] != target[2], zerocoords) # 同一列を取り除く
                push!(cols, target[2])
            end
        end
        # 残りの領域
        indices = filter(coord->coord[1] ∉ rows && coord[2] ∉ cols, CartesianIndices(M))
        crosspoints = CartesianIndex.((Base.product(rows, cols)))
        x_min = minimum(M[indices])
        M[indices] .-= x_min
        M[crosspoints] .+= x_min
    end
end

function getpairs(M)
    selects = CartesianIndex[]
    # 0要素index取得
    indices = findall(x->iszero(x), M)
    nrow = size(M, 1)
    for i in 1:nrow
        zerocoords = filter(coord->coord[1] == i, indices)
        # zerocoordsがない場合はpair計算不可能
        if isempty(zerocoords)
            return nothing
        end
        # 行について,0がある列の0の個数を計算
        # 最小の0の個数の要素を選択する
        zeros = map(zerocoords) do target
            count(coord->coord[2] == target[2], indices)
        end
        # 選択する0要素
        selected = zerocoords[argmin(zeros)]
        push!(selects, selected)
        # 同一列を取り除く
            indices = filter(coord->coord[2]!=selected[2], indices)
    end
    return selects
end

# 例
testmat = [5 4 7 6; 6 7 3 2; 8 11 2 5; 9 8 6 8]
x = hungarian(testmat)

ライブラリは存在する
FugroRoames/Munkres.jl: Munkres algorithm for the optimal assignment problem (github.com)

ステップ3について

  • 行または列のうち, 0が最大個数含まれる方を選択していく
  • 対象の0の要素として,その行の0の数と,その列の0の数を計算する

関連

  • [[二部マッチング問題]]

参考

コメント