I should use the divide-and-conquer paradigm to design a recursive algorithm "CBCover", which determines a coverage (as seen in the image below) in runtime O(n^2) (n = 2^k).
The entry/ input of CBCover should be (k, a, b), where k defines the size of the chessboard, and (a, b) are the coordinates of the missing field.
Possible coverage:
4x4 chessboard missing (4, 1):
Does anyone have any idea what the pseudocode could look like?
The algorithm can be as follows:
Determine which of the four quadrants has the missing field. Position an L-piece in the centre of the grid such that it occupies one field in each of the three other quadrants. Now you have four quadrants with each a field that cannot be used any more. Solve each of those quadrants recursively, applying the same strategy. The recursion stops when the current grid has no available field, i.e. it is a 1x1 grid consisting of a field that is not available.
There are different possible data structures you could use to describe the tessalation. One way is to create a 2D grid, where each cell gets a value that uniquely identifies the shape it belongs to. So cells with the same value belong together.
The above algorithm could start with a grid that first assigns a unique value to each cell (0, 1, 2, ...etc), and then copies the value from one cell to another when they must belong to the same shape.
Here is an implementation in simple JavaScript. It is interactive, so you can change the input by clicking the buttons, and by hovering the mouse over the grid, you identify the "missing field":
// Main algorithm:
function tile(k, a, b) {
let length = 2**k;
// Create a grid where each cell has a unique value
let grid = [];
for (let y = 0; y < length; y++) {
let row = [];
for (let x = 0; x < length; x++) {
row.push(y*length + x); // unique value
}
grid.push(row);
}
a = a % length;
b = b % length;
function recur(length, a, b, top, left) {
if (length == 1) return;
let half = length / 2;
let midrow = top + half;
let midcol = left + half;
let quadrant = (a >= midrow) * 2 + (b >= midcol);
let val = -1;
for (let i = 0; i < 4; i++) {
let quadTop = i >= 2 ? midrow : top;
let quadLeft = i % 2 ? midcol : left;
let row, col;
if (quadrant == i) {
row = a;
col = b;
} else {
row = midrow - 1 + (i >> 1);
col = midcol - 1 + (i % 2);
// Add this cell to an L-shape
if (val == -1) val = grid[row][col];
else grid[row][col] = val; // join with neighboring cell
}
recur(half, row, col, quadTop, quadLeft);
}
}
recur(length, a, b, 0, 0);
return grid;
}
// Parameters of the problem
let k, a, b;
// I/O handling:
function solve(newK, newA, newB) {
if (newK <= 0) return; // grid must be at least 2x2
k = newK;
a = newA;
b = newB;
let grid = tile(k, a, b);
display(grid);
}
let table = document.querySelector("table");
function display(grid) {
table.innerHTML = "";
for (let y = 0; y < grid.length; y++) {
let tr = table.insertRow();
for (let x = 0; x < grid.length; x++) {
let val = grid[y][x];
cls = "";
if (y && val === grid[y-1][x]) cls += " up";
if (grid[y+1] && val === grid[y+1][x]) cls += " down";
if (val === grid[y][x-1]) cls += " left";
if (val === grid[y][x+1]) cls += " right";
if (cls === "") cls = "gap";
tr.insertCell().className = cls.trim();
}
}
}
// Allow user to select gap with a click on a cell:
table.addEventListener("mousemove", (e) => {
let td = e.target;
if (td.tagName !== "TD") return;
solve(k, td.parentNode.rowIndex, td.cellIndex);
});
// Allow user to change the size of the grid:
document.querySelector("#dbl").addEventListener("click", () => solve(k+1, a, b));
document.querySelector("#hlf").addEventListener("click", () => solve(k-1, a, b));
// Create, solve and display initial grid
solve(2, 0, 0);
table { border-collapse: collapse }
td { border: 1px solid; width: 10px; height: 10px }
.gap { background: silver }
.up { border-top-color: transparent }
.down { border-bottom-color: transparent }
.left { border-left-color: transparent }
.right { border-right-color: transparent }
<button id="dbl">Increase size</button><button id="hlf">Decrease size</button><br><br>
<table></table><br>
The scenario looks something like this:
Given an array of 1's and 2's, we want to make it all ones. There are following constraints:
In every step, one of the 2's could exchange its position with the adjacent one.
1 2 1 2 1
could be transformed to:
2 1 1 2 1
in single step.
If a 2 appears at the edge, it could be broken down into 2 ones in a single step.
2 1 2 1
1 1 1 2 1
in single step
If two twos are adjacent they could be broken down into ones.
1 2 2 1
into
1 *1 1 1 1* 1
it costs us 2 steps.
So the problem is, given n steps can we make it all one?
I don't want the complete answers some small insight would also do.
Here is a constructive solution.
Lemma: It's never optimal to broke more than one 2 near the border (both left and right).
Proof: Assume in optimal solution we've broken the two leftmost 2s near the left border and their positions in array were x and y (x <= y). We used x + y + 2 operations to brake them up. But if we just move them in adjacent tiles and perform 3rd type of operation, we achieve only y - x + 1 operations. So previous solution wasn't optimal. Proved.
So there is just 4 cases:
Don't use 2nd operation at all (only if number of 2 is even).
Use 2nd operation only at left border (only if number of 2 is odd).
Use 2nd operation only at right border (only if number of 2 is odd).
Use 2nd operation at both borders (only if number of 2 is even).
After upper process we have even number of 2. So just pair them and brake via 3rd operation.
The complexity of solution is O(n), since we have O(n) input data it's the most optimal way to deal this problem.
Feel free to ask questions or code.
I tried to develop dynamic programming algorithm but could not find the optimal substructure.
This is the brute force, most time consuming, recursive solution.
We have to find whether we can reduce input to all 1's.
Input array has 1's and 2's and there are rules to convert 2's into 1's.
The naive approach is to apply the rule wherever possible and try to find out whether the output can be achieved
The pseudo-code-ey algorithm is :
boolean check(int[] input, int steps) {
if(steps == 0) {
return allOnes(input)
}
step--
boolean ans = false;
if(input[0] == 2) {
breakEdgeTwoIntoOne(input,0) // function for rule 2
revert(input) // convert input to original
ans = ans | check(input,steps)
}
if(input[input.length-1] == 2) {
breakEdgeTwoIntoOne(input,input.length - 1)
revert(input)
ans = ans | check(input,steps)
}
for(i=0;i<input.length-1;i++) {
if(input[i] == 2 && input[i+1] == 2) {
breakTowAdjecentTwosIntoOnes(input,i) // function for rule 3
revert(input)
ans = ans | check(input,steps)
}
}
for(i=0;i<input.length-1;i++) {
if( input[i] == 2 ) {
swapLeft(input, i) // function for rule 1
ans = ans | check(input,steps)
revert(input)
swapRight(input, i)
ans = ans | check(input,steps)
revert(input)
}
}
return ans;
}
The not-so-elegnet java implementation for all the functions :
static boolean check(int[] input, int steps) {
if (allOnes(input)) {
return true;
}
if (steps == 0) {
return allOnes(input);
}
steps--;
int original[] = new int[input.length];
System.arraycopy(input, 0, original, 0, input.length);
boolean ans = false;
if (input[0] == 2) {
ans = ans | check(breakEdgeTwoIntoOne(input, 0), steps);
System.arraycopy(original, 0, input, 0, input.length);
}
if (input[input.length - 1] == 2) {
ans = ans | check(breakEdgeTwoIntoOne(input, input.length - 1), steps);
System.arraycopy(original, 0, input, 0, input.length);
}
for (int i = 0; i < input.length - 1; i++) {
if (input[i] == 2 && input[i + 1] == 2) {
ans = ans | check(breakTowAdjecentTwosIntoOnes(input, i), steps);
System.arraycopy(original, 0, input, 0, input.length);
}
}
for (int i = 0; i < input.length - 1; i++) {
if (input[i] == 2) {
if (i != 0) {
ans = ans | check(swapTwoToLeft(input, i), steps);
System.arraycopy(original, 0, input, 0, input.length);
}
if (i != input.length - 1) {
ans = ans | check(swapTwoToRight(input, i), steps);
System.arraycopy(original, 0, input, 0, input.length);
}
}
}
return ans;
}
private static int[] breakTowAdjecentTwosIntoOnes(int[] input, int i) {
int op[] = new int[input.length + 2];
int k = 0;
for (int j = 0; j < input.length; j++) {
if (j == i || j == i + 1) {
op[k++] = 1;
op[k++] = 1;
// j++;
} else {
op[k++] = input[j];
}
}
return op;
}
private static int[] breakEdgeTwoIntoOne(int[] input, int i) {
int op[] = new int[input.length + 1];
if (i == 0) {
op[0] = 1;
op[1] = 1;
System.arraycopy(input, 1, op, 2, input.length - 1);
} else {
op[op.length - 2] = 1;
op[op.length - 1] = 1;
System.arraycopy(input, 0, op, 0, input.length - 1);
}
return op;
}
private static int[] swapTwoToRight(int[] input, int i) {
if (i != input.length - 1) {
int k = input[i + 1];
input[i + 1] = input[i];
input[i] = k;
}
return input;
}
private static int[] swapTwoToLeft(int[] input, int i) {
if (i != 0) {
int k = input[i - 1];
input[i - 1] = input[i];
input[i] = k;
}
return input;
}
private static boolean allOnes(int[] input) {
for (int x : input) {
if (x != 1)
return false;
}
return true;
}
Some of test cases
[1,2,1,2,1],1 => false
[1,2,1,2,1],2 => true
[1,2,1,2,1],3 => true
[2,2,2],1 => false
[2,2,2],2 => true
[2,2,2],3 => true
[2,2,2],4 => true
I am trying to implement the Hungarian Algorithm but I am stuck on the step 5. Basically, given a n X n matrix of numbers, how can I find minimum number of vertical+horizontal lines such that the zeroes in the matrix are covered?
Before someone marks this question as a duplicate of this, the solution mentioned there is incorrect and someone else also ran into the bug in the code posted there.
I am not looking for code but rather the concept by which I can draw these lines...
EDIT:
Please do not post the simple (but wrong) greedy algorithm:
Given this input:
(0, 1, 0, 1, 1)
(1, 1, 0, 1, 1)
(1, 0, 0, 0, 1)
(1, 1, 0, 1, 1)
(1, 0, 0, 1, 0)
I select, column 2 obviously (0-indexed):
(0, 1, x, 1, 1)
(1, 1, x, 1, 1)
(1, 0, x, 0, 1)
(1, 1, x, 1, 1)
(1, 0, x, 1, 0)
Now I can either select row 2 or col 1 both of which have two "remaining" zeroes. If I select col2, I end up with incorrect solution down this path:
(0, x, x, 1, 1)
(1, x, x, 1, 1)
(1, x, x, 0, 1)
(1, x, x, 1, 1)
(1, x, x, 1, 0)
The correct solution is using 4 lines:
(x, x, x, x, x)
(1, 1, x, 1, 1)
(x, x, x, x, x)
(1, 1, x, 1, 1)
(x, x, x, x, x)
Update
I have implemented the Hungarian Algorithm in the same steps provided by the link you posted: Hungarian algorithm
Here's the files with comments:
Github
Algorithm (Improved greedy) for step 3: (This code is very detailed and good for understanding the concept of choosing line to draw: horizontal vs Vertical. But note that this step code is improved in my code in Github)
Calculate the max number of zeros vertically vs horizontally for each xy position in the input matrix and store the result in a separate array called m2.
While calculating, if horizontal zeros > vertical zeroes, then the calculated number is converted to negative. (just to distinguish which direction we chose for later use)
Loop through all elements in the m2 array. If the value is positive, draw a vertical line in array m3, if value is negative, draw an horizontal line in m3
Follow the below example + code to understand more the algorithm:
Create 3 arrays:
m1: First array, holds the input values
m2: Second array, holds maxZeroes(vertical,horizontal) at each x,y position
m3: Third array, holds the final lines (0 index uncovered, 1 index covered)
Create 2 functions:
hvMax(m1,row,col); returns maximum number of zeroes horizontal or vertical. (Positive number means vertical, negative number means horizontal)
clearNeighbours(m2, m3,row,col); void method, it will clear the horizontal neighbors if the value at row col indexes is negative, or clear vertical neighbors if positive. Moreover, it will set the line in the m3 array, by flipping the zero bit to 1.
Code
public class Hungarian {
public static void main(String[] args) {
// m1 input values
int[][] m1 = { { 0, 1, 0, 1, 1 }, { 1, 1, 0, 1, 1 }, { 1, 0, 0, 0, 1 },
{ 1, 1, 0, 1, 1 }, { 1, 0, 0, 1, 0 } };
// int[][] m1 = { {13,14,0,8},
// {40,0,12,40},
// {6,64,0,66},
// {0,1,90,0}};
// int[][] m1 = { {0,0,100},
// {50,100,0},
// {0,50,50}};
// m2 max(horizontal,vertical) values, with negative number for
// horizontal, positive for vertical
int[][] m2 = new int[m1.length][m1.length];
// m3 where the line are drawen
int[][] m3 = new int[m1.length][m1.length];
// loop on zeroes from the input array, and sotre the max num of zeroes
// in the m2 array
for (int row = 0; row < m1.length; row++) {
for (int col = 0; col < m1.length; col++) {
if (m1[row][col] == 0)
m2[row][col] = hvMax(m1, row, col);
}
}
// print m1 array (Given input array)
System.out.println("Given input array");
for (int row = 0; row < m1.length; row++) {
for (int col = 0; col < m1.length; col++) {
System.out.print(m1[row][col] + "\t");
}
System.out.println();
}
// print m2 array
System.out
.println("\nm2 array (max num of zeroes from horizontal vs vertical) (- for horizontal and + for vertical)");
for (int row = 0; row < m1.length; row++) {
for (int col = 0; col < m1.length; col++) {
System.out.print(m2[row][col] + "\t");
}
System.out.println();
}
// Loop on m2 elements, clear neighbours and draw the lines
for (int row = 0; row < m1.length; row++) {
for (int col = 0; col < m1.length; col++) {
if (Math.abs(m2[row][col]) > 0) {
clearNeighbours(m2, m3, row, col);
}
}
}
// prinit m3 array (Lines array)
System.out.println("\nLines array");
for (int row = 0; row < m1.length; row++) {
for (int col = 0; col < m1.length; col++) {
System.out.print(m3[row][col] + "\t");
}
System.out.println();
}
}
// max of vertical vs horizontal at index row col
public static int hvMax(int[][] m1, int row, int col) {
int vertical = 0;
int horizontal = 0;
// check horizontal
for (int i = 0; i < m1.length; i++) {
if (m1[row][i] == 0)
horizontal++;
}
// check vertical
for (int i = 0; i < m1.length; i++) {
if (m1[i][col] == 0)
vertical++;
}
// negative for horizontal, positive for vertical
return vertical > horizontal ? vertical : horizontal * -1;
}
// clear the neighbors of the picked largest value, the sign will let the
// app decide which direction to clear
public static void clearNeighbours(int[][] m2, int[][] m3, int row, int col) {
// if vertical
if (m2[row][col] > 0) {
for (int i = 0; i < m2.length; i++) {
if (m2[i][col] > 0)
m2[i][col] = 0; // clear neigbor
m3[i][col] = 1; // draw line
}
} else {
for (int i = 0; i < m2.length; i++) {
if (m2[row][i] < 0)
m2[row][i] = 0; // clear neigbor
m3[row][i] = 1; // draw line
}
}
m2[row][col] = 0;
m3[row][col] = 1;
}
}
Output
Given input array
0 1 0 1 1
1 1 0 1 1
1 0 0 0 1
1 1 0 1 1
1 0 0 1 0
m2 array (max num of zeroes from horizontal vs vertical) (- for horizontal and + for vertical)
-2 0 5 0 0
0 0 5 0 0
0 -3 5 -3 0
0 0 5 0 0
0 -3 5 0 -3
Lines array
1 1 1 1 1
0 0 1 0 0
1 1 1 1 1
0 0 1 0 0
1 1 1 1 1
PS: Your example that you pointed to, will never occur because as you can see the first loop do the calculations by taking the max(horizontal,vertical) and save them in m2. So col1 will not be selected because -3 means draw horizontal line, and -3 was calculated by taking the max between horizontal vs vertical zeros. So at the first iteration at the elements, the program has checked how to draw the lines, on the second iteration, the program draw the lines.
Greedy algorithms may not work for some cases.
Firstly, it is possible reformulate your problem as following: given a bipartite graph, find a minimum vertex cover. In this problem there are 2n nodes, n for rows and n for columns. There is an edge between two nodes if element at the intersection of corresponding column and row is zero. Vertex cover is a set of nodes (rows and columns) such that each edge is incident to some node from that set (each zero is covered by row or column).
This is a well known problem and can be solved in O(n^3) by finding a maximum matching. Check wikipedia for details
There are cases where Amir's code fails.
Consider the following m1:
0 0 1
0 1 1
1 0 1
The best solution is to draw vertical lines in the first two columns.
Amir's code would give the following m2:
-2 -2 0
2 0 0
0 2 0
And the result would draw the two vertical lines AS WELL AS a line in the first row.
It seems to me the problem is the tie-breaking case:
return vertical > horizontal ? vertical : horizontal * -1;
Because of the way the code is written, the very similar m1 will NOT fail:
0 1 1
1 0 1
0 0 1
Where the first row is moved to the bottom, because the clearing function will clear the -2 values from m2 before those cells are reached. In the first case, the -2 values are hit first, so a horizontal line is drawn through the first row.
I've been working a little through this, and this is what I have. In the case of a tie, do not set any value and do not draw a line through those cells. This covers the case of the matrix I mentioned above, we are done at this step.
Clearly, there are situations where there will remain 0s that are uncovered. Below is another example of a matrix that will fail in Amir's method (m1):
0 0 1 1 1
0 1 0 1 1
0 1 1 0 1
1 1 0 0 1
1 1 1 1 1
The optimal solution is four lines, e.g. the first four columns.
Amir's method gives m2:
3 -2 0 0 0
3 0 -2 0 0
3 0 0 -2 0
0 0 -2 -2 0
0 0 0 0 0
Which will draw lines at the first four rows and the first column (an incorrect solution, giving 5 lines). Again, the tie-breaker case is the issue. We solve this by not setting a value for the ties, and iterating the procedure.
If we ignore the ties we get an m2:
3 -2 0 0 0
3 0 0 0 0
3 0 0 0 0
0 0 0 0 0
0 0 0 0 0
This leads to covering only the first row and the first column. We then take out the 0s that are covered to give new m1:
1 1 1 1 1
1 1 0 1 1
1 1 1 0 1
1 1 0 0 1
1 1 1 1 1
Then we keep repeating the procedure (ignoring ties) until we reach a solution. Repeat for a new m2:
0 0 0 0 0
0 0 2 0 0
0 0 0 2 0
0 0 0 0 0
0 0 0 0 0
Which leads to two vertical lines through the second and third columns. All 0s are now covered, needing only four lines (this is an alternative to lining the first four columns). The above matrix only needs 2 iterations, and I imagine most of these cases will need only two iterations unless there are sets of ties nested within sets of ties. I tried to come up with one, but it became difficult to manage.
Sadly, this is not good enough, because there will be cases that will remain tied forever. Particularly, in cases where there is a 'disjoint set of tied cells'. Not sure how else to describe this except to draw the following two examples:
0 0 1 1
0 1 1 1
1 0 1 1
1 1 1 0
or
0 0 1 1 1
0 1 1 1 1
1 0 1 1 1
1 1 1 0 0
1 1 1 0 0
The upper-left 3x3 sub-matrices in these two examples are identical to my original example, I have added 1 or 2 rows/cols to that example at the bottom and right. The only newly added zeros are where the new rows and columns cross. Describing for clarity.
With the iterative method I described, these matrices will be caught in an infinite loop. The zeros will always remain tied (col-count vs row-count). At this point, it does make sense to just arbitrarily choose a direction in the case of a tie, at least from what I can imagine.
The only issue I'm running into is setting up the stopping criteria for the loop. I can't assume that 2 iterations is enough (or any n), but I also can't figure out how to detect if a matrix has only infinite loops left within it. I'm still not sure how to describe these disjoint-tied-sets computationally.
Here is the code to do what I have come up with so far (in MATLAB script):
function [Lines, AllRows, AllCols] = FindMinLines(InMat)
%The following code finds the minimum set of lines (rows and columns)
%required to cover all of the true-valued cells in a matrix. If using for
%the Hungarian problem where 'true-values' are equal to zero, make the
%necessary changes. This code is not complete, since it will be caught in
%an infinite loop in the case of disjoint-tied-sets
%If passing in a matrix where 0s are the cells of interest, uncomment the
%next line
%InMat = InMat == 0;
%Assume square matrix
Count = length(InMat);
Lines = zeros(Count);
%while there are any 'true' values not covered by lines
while any(any(~Lines & InMat))
%Calculate row-wise and col-wise totals of 'trues' not-already-covered
HorzCount = repmat(sum(~Lines & InMat, 2), 1, Count).*(~Lines & InMat);
VertCount = repmat(sum(~Lines & InMat, 1), Count, 1).*(~Lines & InMat);
%Calculate for each cell the difference between row-wise and col-wise
%counts. I.e. row-oriented cells will have a negative number, col-oriented
%cells will have a positive numbers, ties and 'non-trues' will be 0.
%Non-zero values indicate lines to be drawn where orientation is determined
%by sign.
DiffCounts = VertCount - HorzCount;
%find the row and col indices of the lines
HorzIdx = any(DiffCounts < 0, 2);
VertIdx = any(DiffCounts > 0, 1);
%Set the horizontal and vertical indices of the Lines matrix to true
Lines(HorzIdx, :) = true;
Lines(:, VertIdx) = true;
end
%compute index numbers to be returned.
AllRows = [find(HorzIdx); find(DisjTiedRows)];
AllCols = find(VertIdx);
end
Step 5:
The drawing of line in the matrix is evaluated diagonally with a maximum evaluations of the length of the matrix.
Based on http://www.wikihow.com/Use-the-Hungarian-Algorithm with Steps 1 - 8 only.
Run code snippet and see results in console
Console Output
horizontal line (row): {"0":0,"2":2,"4":4}
vertical line (column): {"2":2}
Step 5: Matrix
0 1 0 1 1
1 1 0 1 1
1 0 0 0 1
1 1 0 1 1
1 0 0 1 0
Smallest number in uncovered matrix: 1
Step 6: Matrix
x x x x x
1 1 x 1 1
x x x x x
1 1 x 1 1
x x x x x
JSFiddle: http://jsfiddle.net/jjcosare/6Lpz5gt9/2/
// http://www.wikihow.com/Use-the-Hungarian-Algorithm
var inputMatrix = [
[0, 1, 0, 1, 1],
[1, 1, 0, 1, 1],
[1, 0, 0, 0, 1],
[1, 1, 0, 1, 1],
[1, 0, 0, 1, 0]
];
//var inputMatrix = [
// [10, 19, 8, 15],
// [10, 18, 7, 17],
// [13, 16, 9, 14],
// [12, 19, 8, 18],
// [14, 17, 10, 19]
// ];
var matrix = inputMatrix;
var HungarianAlgorithm = {};
HungarianAlgorithm.step1 = function(stepNumber) {
console.log("Step " + stepNumber + ": Matrix");
var currentNumber = 0;
for (var i = 0; i < matrix.length; i++) {
var sb = "";
for (var j = 0; j < matrix[i].length; j++) {
currentNumber = matrix[i][j];
sb += currentNumber + " ";
}
console.log(sb);
}
}
HungarianAlgorithm.step2 = function() {
var largestNumberInMatrix = getLargestNumberInMatrix(matrix);
var rowLength = matrix.length;
var columnLength = matrix[0].length;
var dummyMatrixToAdd = 0;
var isAddColumn = rowLength > columnLength;
var isAddRow = columnLength > rowLength;
if (isAddColumn) {
dummyMatrixToAdd = rowLength - columnLength;
for (var i = 0; i < rowLength; i++) {
for (var j = columnLength; j < (columnLength + dummyMatrixToAdd); j++) {
matrix[i][j] = largestNumberInMatrix;
}
}
} else if (isAddRow) {
dummyMatrixToAdd = columnLength - rowLength;
for (var i = rowLength; i < (rowLength + dummyMatrixToAdd); i++) {
matrix[i] = [];
for (var j = 0; j < columnLength; j++) {
matrix[i][j] = largestNumberInMatrix;
}
}
}
HungarianAlgorithm.step1(2);
console.log("Largest number in matrix: " + largestNumberInMatrix);
function getLargestNumberInMatrix(matrix) {
var largestNumberInMatrix = 0;
var currentNumber = 0;
for (var i = 0; i < matrix.length; i++) {
for (var j = 0; j < matrix[i].length; j++) {
currentNumber = matrix[i][j];
largestNumberInMatrix = (largestNumberInMatrix > currentNumber) ?
largestNumberInMatrix : currentNumber;
}
}
return largestNumberInMatrix;
}
}
HungarianAlgorithm.step3 = function() {
var smallestNumberInRow = 0;
var currentNumber = 0;
for (var i = 0; i < matrix.length; i++) {
smallestNumberInRow = getSmallestNumberInRow(matrix, i);
console.log("Smallest number in row[" + i + "]: " + smallestNumberInRow);
for (var j = 0; j < matrix[i].length; j++) {
currentNumber = matrix[i][j];
matrix[i][j] = currentNumber - smallestNumberInRow;
}
}
HungarianAlgorithm.step1(3);
function getSmallestNumberInRow(matrix, rowIndex) {
var smallestNumberInRow = matrix[rowIndex][0];
var currentNumber = 0;
for (var i = 0; i < matrix.length; i++) {
currentNumber = matrix[rowIndex][i];
smallestNumberInRow = (smallestNumberInRow < currentNumber) ?
smallestNumberInRow : currentNumber;
}
return smallestNumberInRow;
}
}
HungarianAlgorithm.step4 = function() {
var smallestNumberInColumn = 0;
var currentNumber = 0;
for (var i = 0; i < matrix.length; i++) {
smallestNumberInColumn = getSmallestNumberInColumn(matrix, i);
console.log("Smallest number in column[" + i + "]: " + smallestNumberInColumn);
for (var j = 0; j < matrix[i].length; j++) {
currentNumber = matrix[j][i];
matrix[j][i] = currentNumber - smallestNumberInColumn;
}
}
HungarianAlgorithm.step1(4);
function getSmallestNumberInColumn(matrix, columnIndex) {
var smallestNumberInColumn = matrix[0][columnIndex];
var currentNumber = 0;
for (var i = 0; i < matrix.length; i++) {
currentNumber = matrix[i][columnIndex];
smallestNumberInColumn = (smallestNumberInColumn < currentNumber) ?
smallestNumberInColumn : currentNumber;
}
return smallestNumberInColumn;
}
}
var rowLine = {};
var columnLine = {};
HungarianAlgorithm.step5 = function() {
var zeroNumberCountRow = 0;
var zeroNumberCountColumn = 0;
rowLine = {};
columnLine = {};
for (var i = 0; i < matrix.length; i++) {
zeroNumberCountRow = getZeroNumberCountInRow(matrix, i);
zeroNumberCountColumn = getZeroNumberCountInColumn(matrix, i);
if (zeroNumberCountRow > zeroNumberCountColumn) {
rowLine[i] = i;
if (zeroNumberCountColumn > 1) {
columnLine[i] = i;
}
} else if (zeroNumberCountRow < zeroNumberCountColumn) {
columnLine[i] = i;
if (zeroNumberCountRow > 1) {
rowLine[i] = i;
}
} else {
if ((zeroNumberCountRow + zeroNumberCountColumn) > 2) {
rowLine[i] = i;
columnLine[i] = i;
}
}
}
var zeroCount = 0;
for (var i in columnLine) {
zeroCount = getZeroNumberCountInColumnLine(matrix, columnLine[i], rowLine);
if (zeroCount == 0) {
delete columnLine[i];
}
}
for (var i in rowLine) {
zeroCount = getZeroNumberCountInRowLine(matrix, rowLine[i], columnLine);
if (zeroCount == 0) {
delete rowLine[i];
}
}
console.log("horizontal line (row): " + JSON.stringify(rowLine));
console.log("vertical line (column): " + JSON.stringify(columnLine));
HungarianAlgorithm.step1(5);
//if ((Object.keys(rowLine).length + Object.keys(columnLine).length) == matrix.length) {
// TODO:
// HungarianAlgorithm.step9();
//} else {
// HungarianAlgorithm.step6();
// HungarianAlgorithm.step7();
// HungarianAlgorithm.step8();
//}
function getZeroNumberCountInColumnLine(matrix, columnIndex, rowLine) {
var zeroNumberCount = 0;
var currentNumber = 0;
for (var i = 0; i < matrix.length; i++) {
currentNumber = matrix[i][columnIndex];
if (currentNumber == 0 && !(rowLine[i] == i)) {
zeroNumberCount++
}
}
return zeroNumberCount;
}
function getZeroNumberCountInRowLine(matrix, rowIndex, columnLine) {
var zeroNumberCount = 0;
var currentNumber = 0;
for (var i = 0; i < matrix.length; i++) {
currentNumber = matrix[rowIndex][i];
if (currentNumber == 0 && !(columnLine[i] == i)) {
zeroNumberCount++
}
}
return zeroNumberCount;
}
function getZeroNumberCountInColumn(matrix, columnIndex) {
var zeroNumberCount = 0;
var currentNumber = 0;
for (var i = 0; i < matrix.length; i++) {
currentNumber = matrix[i][columnIndex];
if (currentNumber == 0) {
zeroNumberCount++
}
}
return zeroNumberCount;
}
function getZeroNumberCountInRow(matrix, rowIndex) {
var zeroNumberCount = 0;
var currentNumber = 0;
for (var i = 0; i < matrix.length; i++) {
currentNumber = matrix[rowIndex][i];
if (currentNumber == 0) {
zeroNumberCount++
}
}
return zeroNumberCount;
}
}
HungarianAlgorithm.step6 = function() {
var smallestNumberInUncoveredMatrix = getSmallestNumberInUncoveredMatrix(matrix, rowLine, columnLine);
console.log("Smallest number in uncovered matrix: " + smallestNumberInUncoveredMatrix);
var columnIndex = 0;
for (var i in columnLine) {
columnIndex = columnLine[i];
for (var i = 0; i < matrix.length; i++) {
currentNumber = matrix[i][columnIndex];
//matrix[i][columnIndex] = currentNumber + smallestNumberInUncoveredMatrix;
matrix[i][columnIndex] = "x";
}
}
var rowIndex = 0;
for (var i in rowLine) {
rowIndex = rowLine[i];
for (var i = 0; i < matrix.length; i++) {
currentNumber = matrix[rowIndex][i];
//matrix[rowIndex][i] = currentNumber + smallestNumberInUncoveredMatrix;
matrix[rowIndex][i] = "x";
}
}
HungarianAlgorithm.step1(6);
function getSmallestNumberInUncoveredMatrix(matrix, rowLine, columnLine) {
var smallestNumberInUncoveredMatrix = null;;
var currentNumber = 0;
for (var i = 0; i < matrix.length; i++) {
if (rowLine[i]) {
continue;
}
for (var j = 0; j < matrix[i].length; j++) {
if (columnLine[j]) {
continue;
}
currentNumber = matrix[i][j];
if (!smallestNumberInUncoveredMatrix) {
smallestNumberInUncoveredMatrix = currentNumber;
}
smallestNumberInUncoveredMatrix =
(smallestNumberInUncoveredMatrix < currentNumber) ?
smallestNumberInUncoveredMatrix : currentNumber;
}
}
return smallestNumberInUncoveredMatrix;
}
}
HungarianAlgorithm.step7 = function() {
var smallestNumberInMatrix = getSmallestNumberInMatrix(matrix);
console.log("Smallest number in matrix: " + smallestNumberInMatrix);
var currentNumber = 0;
for (var i = 0; i < matrix.length; i++) {
for (var j = 0; j < matrix[i].length; j++) {
currentNumber = matrix[j][i];
matrix[j][i] = currentNumber - smallestNumberInMatrix;
}
}
HungarianAlgorithm.step1(7);
function getSmallestNumberInMatrix(matrix) {
var smallestNumberInMatrix = matrix[0][0];
var currentNumber = 0;
for (var i = 0; i < matrix.length; i++) {
for (var j = 0; j < matrix[i].length; j++) {
currentNumber = matrix[i][j];
smallestNumberInMatrix = (smallestNumberInMatrix < currentNumber) ?
smallestNumberInMatrix : currentNumber;
}
}
return smallestNumberInMatrix;
}
}
HungarianAlgorithm.step8 = function() {
console.log("Step 8: Covering zeroes with Step 5 - 8 until Step 9 is reached");
HungarianAlgorithm.step5();
}
HungarianAlgorithm.step9 = function(){
console.log("Step 9...");
}
HungarianAlgorithm.step1(1);
HungarianAlgorithm.step2();
HungarianAlgorithm.step3();
HungarianAlgorithm.step4();
HungarianAlgorithm.step5();
HungarianAlgorithm.step6();
Do the assignment using the steps mentioned below:
assign a row if it has only one 0, else skip the row temporarily
cross out the 0's in the assigned column
Do the same for every column
After doing the assignment using the above steps, follow the steps below to get the minimum number of lines which cover all the 0's
step 1 - Tick an unassigned row
step 2 - If a ticked row has a 0, then tick the corresponding column
step 3 - If a ticked column has an assignment, then tick the corresponding row
step 4 - Repeat steps 2 and 3, till no more ticking is possible
step 5 - Draw lines through un-ticked rows and ticked columns
For your case: (0-indexing for rows and columns)
skip row 0, as it has two 0's
assign row 1, and cross out all the 0's in column 2
skip row 2, as it has two uncrossed 0's
skip row 3, as it has no uncrossed 0
skip row 4, as it has 2 uncrossed 0's
assign column 0
skip column 1 as it has two uncrossed 0's (in row-2 and row-4)
skip column 2, as it has an already assigned 0
assign column 3,and cross out the 0 in row 2
assign column 4, and cross out the 0 in row 4
assigned 0's are shown by '_' and 'x' shows crossed out 0's
( _ 1 x 1 1 ),
( 1 1 _ 1 1 ),
( 1 x x _ 1 ),
( 1 1 x 1 1 ),
( 1 x x 1 _ )
The matrix looks like the one shown above after doing the assignments
Now follow the 5 steps mentioned above to get the minimum number of lines that cover all the 0's
Tick row 3 as it is not assigned yet
Since row 3 has a 0 in column 2, tick column 2
Since column 2 has an assignment in row 1, tick row 1
Now draw lines through un-ticked rows (i.e. row 0,2,4) and ticked columns(i.e. column 2)
These 4 lines will cover all the 0's
Hope this helps:)
PS : For cases where no initial assignment is possible due to multiple 0's in each row and column, this could be handled by taking one arbitrary assignment (For the cases where multiple 0's are present in each row and column, it is very likely that more than one possible assignment would result in an optimal solution)
#CMPS answer fails on quite a few graphs. I think I have implemented a solution which solves the problem.
I followed the Wikipedia article on the Hungarian algorithm and I made an implementation that seems to work all the time.
From Wikipedia, here is a the method to draw the minimum number of lines:
First, assign as many tasks as possible.
Mark all rows having no assignments.
Mark all (unmarked) columns having zeros in newly marked row(s).
Mark all rows having assignments in newly marked columns.
Repeat for all non-assigned rows.
Here is my Ruby implementation:
def draw_lines grid
#copies the array
marking_grid = grid.map { |a| a.dup }
marked_rows = Array.new
marked_cols = Array.new
while there_is_zero(marking_grid) do
marking_grid = grid.map { |a| a.dup }
marked_cols.each do |col|
cross_out(marking_grid,nil, col)
end
marked = assignment(grid, marking_grid)
marked_rows = marked[0]
marked_cols.concat(marked[1]).uniq!
marking_grid = grid.map { |a| a.dup }
marking_grid.length.times do |row|
if !(marked_rows.include? row) then
cross_out(marking_grid,row, nil)
end
end
marked_cols.each do |col|
cross_out(marking_grid,nil, col)
end
end
lines = Array.new
marked_cols.each do |index|
lines.push(["column", index])
end
grid.each_index do |index|
if !(marked_rows.include? index) then
lines.push(["row", index])
end
end
return lines
end
def there_is_zero grid
grid.each_with_index do |row|
row.each_with_index do |value|
if value == 0 then
return true
end
end
end
return false
end
def assignment grid, marking_grid
marking_grid.each_index do |row_index|
first_zero = marking_grid[row_index].index(0)
#if there is no zero go to next row
if first_zero.nil? then
next
else
cross_out(marking_grid, row_index, first_zero)
marking_grid[row_index][first_zero] = "*"
end
end
return mark(grid, marking_grid)
end
def mark grid, marking_grid, marked_rows = Array.new, marked_cols = Array.new
marking_grid.each_with_index do |row, row_index|
selected_assignment = row.index("*")
if selected_assignment.nil? then
marked_rows.push(row_index)
end
end
marked_rows.each do |index|
grid[index].each_with_index do |cost, col_index|
if cost == 0 then
marked_cols.push(col_index)
end
end
end
marked_cols = marked_cols.uniq
marked_cols.each do |col_index|
marking_grid.each_with_index do |row, row_index|
if row[col_index] == "*" then
marked_rows.push(row_index)
end
end
end
return [marked_rows, marked_cols]
end
def cross_out(marking_grid, row, col)
if col != nil then
marking_grid.each_index do |i|
marking_grid[i][col] = "X"
end
end
if row != nil then
marking_grid[row].map! {|i| "X"}
end
end
grid = [
[0,0,1,0],
[0,0,1,0],
[0,1,1,1],
[0,1,1,1],
]
p draw_lines(grid)