package arkref.ext.fig.basic; import java.util.*; /** * An implementation of the classic hungarian algorithm for the assignment problem. * * Copyright 2007 Gary Baker (GPL v3) * @author gbaker * Modified by pliang 12/28/07, 11/26/08 */ public class BipartiteMatcher { public double[][] copy(double[][] matrix) { double[][] newMatrix = new double[matrix.length][matrix[0].length]; for(int i = 0; i < matrix.length; i++) for(int j = 0; j < matrix[i].length; j++) newMatrix[i][j] = matrix[i][j]; return newMatrix; } public int[] findMaxWeightAssignment(double[][] matrix) { matrix = copy(matrix); for(int i = 0; i < matrix.length; i++) for(int j = 0; j < matrix[i].length; j++) matrix[i][j] = -matrix[i][j]; return findBestAssignment(matrix); } public int[] findMinWeightAssignment(double[][] matrix) { matrix = copy(matrix); return findBestAssignment(matrix); } // Finds a minimum weight assignment // WARNING: modifies matrix public int[] findBestAssignment(double[][] matrix) { // subtract minumum value from rows and columns to create lots of zeroes reduceMatrix(matrix); // non negative values are the index of the starred or primed zero in the row or column int[] starsByRow = new int[matrix.length]; Arrays.fill(starsByRow,-1); int[] starsByCol = new int[matrix[0].length]; Arrays.fill(starsByCol,-1); int[] primesByRow = new int[matrix.length]; Arrays.fill(primesByRow,-1); // 1s mean covered, 0s mean not covered int[] coveredRows = new int[matrix.length]; int[] coveredCols = new int[matrix[0].length]; // star any zero that has no other starred zero in the same row or column initStars(matrix, starsByRow, starsByCol); coverColumnsOfStarredZeroes(starsByCol,coveredCols); while (!allAreCovered(coveredCols)) { int[] primedZero = primeSomeUncoveredZero(matrix, primesByRow, coveredRows, coveredCols); while (primedZero == null) { // keep making more zeroes until we find something that we can prime (i.e. a zero that is uncovered) makeMoreZeroes(matrix,coveredRows,coveredCols); primedZero = primeSomeUncoveredZero(matrix, primesByRow, coveredRows, coveredCols); } // check if there is a starred zero in the primed zero's row int columnIndex = starsByRow[primedZero[0]]; if (-1 == columnIndex){ // if not, then we need to increment the zeroes and start over incrementSetOfStarredZeroes(primedZero, starsByRow, starsByCol, primesByRow); Arrays.fill(primesByRow,-1); Arrays.fill(coveredRows,0); Arrays.fill(coveredCols,0); coverColumnsOfStarredZeroes(starsByCol,coveredCols); } else { // cover the row of the primed zero and uncover the column of the starred zero in the same row coveredRows[primedZero[0]] = 1; coveredCols[columnIndex] = 0; } } // ok now we should have assigned everything // take the starred zeroes in each column as the correct assignments int[] assign = new int[starsByCol.length]; for(int i = 0; i < starsByCol.length; i++) assign[starsByCol[i]] = i; return assign; /*int[][] retval = new int[matrix.length][]; for (int i = 0; i < starsByCol.length; i++) { retval[i] = new int[]{starsByCol[i],i}; } return retval;*/ } private boolean allAreCovered(int[] coveredCols) { for (int covered : coveredCols) { if (0 == covered) return false; } return true; } /** * the first step of the hungarian algorithm * is to find the smallest element in each row * and subtract it's values from all elements * in that row * * @return the next step to perform */ private void reduceMatrix(double[][] matrix) { for (int i = 0; i < matrix.length; i++) { // find the min value in the row double minValInRow = Double.MAX_VALUE; for (int j = 0; j < matrix[i].length; j++) { if (minValInRow > matrix[i][j]) { minValInRow = matrix[i][j]; } } // subtract it from all values in the row for (int j = 0; j < matrix[i].length; j++) { matrix[i][j] -= minValInRow; } } for (int i = 0; i < matrix[0].length; i++) { double minValInCol = Double.MAX_VALUE; for (int j = 0; j < matrix.length; j++) { if (minValInCol > matrix[j][i]) { minValInCol = matrix[j][i]; } } for (int j = 0; j < matrix.length; j++) { matrix[j][i] -= minValInCol; } } } /** * init starred zeroes * * for each column find the first zero * if there is no other starred zero in that row * then star the zero, cover the column and row and * go onto the next column * * @param costMatrix * @param starredZeroes * @param coveredRows * @param coveredCols * @return the next step to perform */ private void initStars(double costMatrix[][], int[] starsByRow, int[] starsByCol) { int [] rowHasStarredZero = new int[costMatrix.length]; int [] colHasStarredZero = new int[costMatrix[0].length]; for (int i = 0; i < costMatrix.length; i++) { for (int j = 0; j < costMatrix[i].length; j++) { if (0 == costMatrix[i][j] && 0 == rowHasStarredZero[i] && 0 == colHasStarredZero[j]) { starsByRow[i] = j; starsByCol[j] = i; rowHasStarredZero[i] = 1; colHasStarredZero[j] = 1; break; // move onto the next row } } } } /** * just marke the columns covered for any coluimn containing a starred zero * @param starsByCol * @param coveredCols */ private void coverColumnsOfStarredZeroes(int[] starsByCol, int[] coveredCols) { for (int i = 0; i < starsByCol.length; i++) { coveredCols[i] = -1 == starsByCol[i] ? 0 : 1; } } /** * finds some uncovered zero and primes it * @param matrix * @param primesByRow * @param coveredRows * @param coveredCols * @return */ private int[] primeSomeUncoveredZero(double matrix[][], int[] primesByRow, int[] coveredRows, int[] coveredCols) { // find an uncovered zero and prime it for (int i = 0; i < matrix.length; i++) { if (1 == coveredRows[i]) continue; for (int j = 0; j < matrix[i].length; j++) { // if it's a zero and the column is not covered if (0 == matrix[i][j] && 0 == coveredCols[j]) { // ok this is an unstarred zero // prime it primesByRow[i] = j; return new int[]{i,j}; } } } return null; } /** * * @param unpairedZeroPrime * @param starsByRow * @param starsByCol * @param primesByRow */ private void incrementSetOfStarredZeroes(int[] unpairedZeroPrime, int[] starsByRow, int[] starsByCol, int[] primesByRow) { // build the alternating zero sequence (prime, star, prime, star, etc) int i, j = unpairedZeroPrime[1]; Set<int[]> zeroSequence = new LinkedHashSet<int[]>(); zeroSequence.add(unpairedZeroPrime); boolean paired = false; do { i = starsByCol[j]; paired = -1 != i && zeroSequence.add(new int[]{i,j}); if (!paired) break; j = primesByRow[i]; paired = -1 != j && zeroSequence.add(new int[]{ i, j }); } while (paired); // unstar each starred zero of the sequence // and star each primed zero of the sequence for (int[] zero : zeroSequence) { if (starsByCol[zero[1]] == zero[0]) { starsByCol[zero[1]] = -1; starsByRow[zero[0]] = -1; } if (primesByRow[zero[0]] == zero[1]) { starsByRow[zero[0]] = zero[1]; starsByCol[zero[1]] = zero[0]; } } } private void makeMoreZeroes(double[][] matrix, int[] coveredRows, int[] coveredCols) { // find the minimum uncovered value double minUncoveredValue = Double.MAX_VALUE; for (int i = 0; i < matrix.length; i++) { if (0 == coveredRows[i]) { for (int j = 0; j < matrix[i].length; j++) { if (0 == coveredCols[j] && matrix[i][j] < minUncoveredValue) { minUncoveredValue = matrix[i][j]; } } } } // add the min value to all covered rows for (int i = 0; i < coveredRows.length; i++) { if (1 == coveredRows[i]) { for (int j = 0; j < matrix[i].length; j++) { matrix[i][j] += minUncoveredValue; } } } // subtract the min value from all uncovered columns for (int i = 0; i < coveredCols.length; i++) { if (0 == coveredCols[i]) { for (int j = 0; j < matrix.length; j++) { matrix[j][i] -= minUncoveredValue; } } } } public static void main(String[] args) { //double[][] mat = { { 3 } }; //double[][] mat = { { 3, 0 }, { 0, 3 } }; double[][] mat = { { 3, 3, 0 }, { 3, 3, 0 }, { 0, 5, 0 }, }; int[] assign = new BipartiteMatcher().findBestAssignment(mat); for(int i = 0; i < assign.length; i++) System.out.println(i + " => " + assign[i]); } }