Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Hungarian Algorithm: How to cover 0 elements with minimum lines?

I am trying to implement the Hungarian algorithm in Java. I have an NxN cost matrix. I am following this guide step by step. So I have the costMatrix[N][N] and 2 arrays to track covered rows and covered cols - rowCover[N], rowColumn[N] (1 means covered, 0 means uncovered)

How can I cover the 0s with the minimum number of lines? Can anyone point me in the right direction?

Any help/suggestion would be appreciated.

like image 297
Ayrton Senna Avatar asked Feb 10 '13 05:02

Ayrton Senna


People also ask

When minimum number of lines required to cover all zeros the number of rows we can get assignment this statement is?

You can cover all the zeros with 2 lines, but this algorithm will tell you to use 3 lines.

Why does the Hungarian algorithm work?

With the cost matrix from the example above in mind, the Hungarian algorithm operates on this key idea: if a number is added to or subtracted from all of the entries of any one row or column of a cost matrix, then an optimal assignment for the resulting cost matrix is also an optimal assignment for the original cost ...


1 Answers

Check the 3rd step of the algorithm in the Wikipedia article (section Matrix Interpretation) , they explain a way to compute the minimal amount of lines to cover all the 0's

Update: The following is another way to obtain the minimum number of lines that cover the 0's:

import java.util.ArrayList;
import java.util.List;

public class MinLines { 
    enum LineType { NONE, HORIZONTAL, VERTICAL }

    private static class Line {
        int lineIndex;
        LineType rowType;
        Line(int lineIndex, LineType rowType) { 
            this.lineIndex = lineIndex;
            this.rowType = rowType;
        }      
        LineType getLineType() {
            return rowType;
        }

        int getLineIndex() { 
            return lineIndex; 
        }
        boolean isHorizontal() {
            return rowType == LineType.HORIZONTAL;
        }
    }

    private static boolean isZero(int[] array) {
        for (int e : array) {
            if (e != 0) {
                return false;
            }
        }
        return true;
    }

    public static List<Line> getMinLines(int[][] matrix) {
        if (matrix.length != matrix[0].length) {
            throw new IllegalArgumentException("Matrix should be square!");
        }

        final int SIZE = matrix.length;
        int[] zerosPerRow = new int[SIZE];
        int[] zerosPerCol = new int[SIZE];

        // Count the number of 0's per row and the number of 0's per column        
        for (int i = 0; i < SIZE; i++) { 
            for (int j = 0; j < SIZE; j++) { 
                if (matrix[i][j] == 0) { 
                    zerosPerRow[i]++;
                    zerosPerCol[j]++;
                }
            }
        }

        // There should be at must SIZE lines, 
        // initialize the list with an initial capacity of SIZE
        List<Line> lines = new ArrayList<Line>(SIZE);

        LineType lastInsertedLineType = LineType.NONE;

        // While there are 0's to count in either rows or colums...
        while (!isZero(zerosPerRow) && !isZero(zerosPerCol)) { 
            // Search the largest count of 0's in both arrays
            int max = -1;
            Line lineWithMostZeros = null;
            for (int i = 0; i < SIZE; i++) {
                // If exists another count of 0's equal to "max" but in this one has
                // the same direction as the last added line, then replace it with this
                // 
                // The heuristic "fixes" the problem reported by @JustinWyss-Gallifent and @hkrish
                if (zerosPerRow[i] > max || (zerosPerRow[i] == max && lastInsertedLineType == LineType.HORIZONTAL)) {
                    lineWithMostZeros = new Line(i, LineType.HORIZONTAL);
                    max = zerosPerRow[i];
                }
            }

            for (int i = 0; i < SIZE; i++) {
                // Same as above
                if (zerosPerCol[i] > max || (zerosPerCol[i] == max && lastInsertedLineType == LineType.VERTICAL)) {
                    lineWithMostZeros = new Line(i, LineType.VERTICAL);
                    max = zerosPerCol[i];
                }
            }

            // Delete the 0 count from the line 
            if (lineWithMostZeros.isHorizontal()) {
                zerosPerRow[lineWithMostZeros.getLineIndex()] = 0; 
            } else {
                zerosPerCol[lineWithMostZeros.getLineIndex()] = 0;
            }

            // Once you've found the line (either horizontal or vertical) with the greater 0's count
            // iterate over it's elements and substract the 0's from the other lines 
            // Example:
            //                          0's x col:
            //           [ 0  1  2  3 ]  ->  1
            //           [ 0  2  0  1 ]  ->  2
            //           [ 0  4  3  5 ]  ->  1
            //           [ 0  0  0  7 ]  ->  3
            //             |  |  |  | 
            //             v  v  v  v
            // 0's x row: {4} 1  2  0 

            //           [ X  1  2  3 ]  ->  0
            //           [ X  2  0  1 ]  ->  1
            //           [ X  4  3  5 ]  ->  0
            //           [ X  0  0  7 ]  ->  2
            //             |  |  |  | 
            //             v  v  v  v
            //            {0} 1  2  0 

            int index = lineWithMostZeros.getLineIndex(); 
            if (lineWithMostZeros.isHorizontal()) {
                for (int j = 0; j < SIZE; j++) {
                    if (matrix[index][j] == 0) {
                        zerosPerCol[j]--;
                    }
                }
            } else {
                for (int j = 0; j < SIZE; j++) {
                    if (matrix[j][index] == 0) {
                        zerosPerRow[j]--;
                    }
                }                    
            }

            // Add the line to the list of lines
            lines.add(lineWithMostZeros); 
            lastInsertedLineType = lineWithMostZeros.getLineType();
        }
        return lines;
    }

    public static void main(String... args) { 
        int[][] example1 = 
        { 
           {0, 1, 0, 0, 5},
           {1, 0, 3, 4, 5},
           {7, 0, 0, 4, 5},
           {9, 0, 3, 4, 5},
           {3, 0, 3, 4, 5} 
        };

        int[][] example2 = 
        {
           {0, 0, 1, 0},
           {0, 1, 1, 0},
           {1, 1, 0, 0},
           {1, 0, 0, 0},
        };

        int[][] example3 = 
        {
           {0, 0, 0, 0, 0, 0},
           {0, 0, 0, 1, 0, 0},
           {0, 0, 1, 1, 0, 0},
           {0, 1, 1, 0, 0, 0},
           {0, 1, 0, 0, 0, 0},
           {0, 0, 0, 0, 0, 0}
        };

        List<int[][]> examples = new ArrayList<int[][]>();
        examples.add(example1);
        examples.add(example2);
        examples.add(example3);

        for (int[][] example : examples) {
            List<Line> minLines = getMinLines(example);
            System.out.printf("Min num of lines for example matrix is: %d\n", minLines.size());
            printResult(example, minLines);
            System.out.println();
        }
    }

    private static void printResult(int[][] matrix, List<Line> lines) {
        if (matrix.length != matrix[0].length) {
            throw new IllegalArgumentException("Matrix should be square!");
        }

        final int SIZE = matrix.length;
        System.out.println("Before:");
        for (int i = 0; i < SIZE; i++) {
            for (int j = 0; j < SIZE; j++) {
                System.out.printf("%d ", matrix[i][j]);
            }
            System.out.println();
        }

        for (Line line : lines) {
            for (int i = 0; i < SIZE; i++) {
                int index = line.getLineIndex();
                if (line.isHorizontal()) {
                    matrix[index][i] = matrix[index][i] < 0 ? -3 : -1;
                } else {
                    matrix[i][index] = matrix[i][index] < 0 ? -3 : -2;
                }
            }
        }   

        System.out.println("\nAfter:");
        for (int i = 0; i < SIZE; i++) {
            for (int j = 0; j < SIZE; j++) {
                System.out.printf("%s ", matrix[i][j] == -1 ? "-" : (matrix[i][j] == -2 ? "|" : (matrix[i][j] == -3 ? "+" : Integer.toString(matrix[i][j]))));
            }
            System.out.println();
        }   
    }
}   

The important part is the getMinLines method, it returns a List with the lines that cover the matrix 0's entries. For the example matrices prints:

Min num of lines for example matrix is: 3
Before:
0 1 0 0 5 
1 0 3 4 5 
7 0 0 4 5 
9 0 3 4 5 
3 0 3 4 5 

After:
- + - - - 
1 | 3 4 5 
- + - - - 
9 | 3 4 5 
3 | 3 4 5 

Min num of lines for example matrix is: 4
Before:
0 0 1 0 
0 1 1 0 
1 1 0 0 
1 0 0 0 

After:
| | | | 
| | | | 
| | | | 
| | | | 

Min num of lines for example matrix is: 6
Before:
0 0 0 0 0 0 
0 0 0 1 0 0 
0 0 1 1 0 0 
0 1 1 0 0 0 
0 1 0 0 0 0 
0 0 0 0 0 0 

After:
- - - - - - 
- - - - - - 
- - - - - - 
- - - - - - 
- - - - - - 
- - - - - -    

I hopes this give you a boost, the rest of the Hungarian algorithm shouldn't be hard to implement

like image 117
higuaro Avatar answered Oct 10 '22 02:10

higuaro