forked from EdgeCaseBerg/Hungarian-Algorithm
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathHungarian.java
247 lines (221 loc) · 9.45 KB
/
Hungarian.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
/**
* Hungarian Algorithm is a combinatorial optimization algorithm that solves the assignment problem in polynomial time. Link: http://en.wikipedia.org/wiki/Hungarian_algorithm
* Coded by Amir El Bawab
* Date: 4 May 2014
* License: MIT License ~ Please read License.txt for more information about the usage of this software
* */
public class Hungarian {
private int[][] originalValues; // Given values
private int[][] values; // Cloned given values to be processed
private int[][] lines; // Line drawn
private int numLines; // Number of line drawn
int rows[]; // Index of the column selected by every row (The final result)
int occupiedCols[]; // Verify that all column are occupied, used in the optimization step
public Hungarian(int[][] matrix) {
// Initialization
originalValues = matrix; // Given matrix
values = cloneMatrix(matrix); // Cloned matrix to be processed
rows = new int[values.length];
occupiedCols = new int[values.length];
//Algorithm
subtractRowMinimal(); // Step 1
subtractColMinimal(); // Step 2
coverZeros(); // Step 3
while(numLines < values.length){
createAdditionalZeros(); // Step 4 (Condition)
coverZeros(); // Step 3 Again (Condition)
}
optimization(); // Optimization
}
/**
* Step 1
* Subtract from every element the minimum value from its row
* */
public void subtractRowMinimal(){
int rowMinValue[] = new int[values.length];
//get the minimum for each row and store in rowMinValue[]
for(int row=0; row<values.length;row++){
rowMinValue[row] = values[row][0];
for(int col=1; col<values.length;col++){
if(values[row][col] < rowMinValue[row])
rowMinValue[row] = values[row][col];
}
}
//subtract minimum from each row using rowMinValue[]
for(int row=0; row<values.length;row++){
for(int col=0; col<values.length;col++){
values[row][col] -= rowMinValue[row];
}
}
} //End Step 1
/**
* Step 2
* Subtract from every element the minimum value from its column
* */
public void subtractColMinimal(){
int colMinValue[] = new int[values.length];
//get the minimum for each column and store them in colMinValue[]
for(int col=0; col<values.length;col++){
colMinValue[col] = values[0][col];
for(int row=1; row<values.length;row++){
if(values[row][col] < colMinValue[col])
colMinValue[col] = values[row][col];
}
}
//subtract minimum from each column using colMinValue[]
for(int col=0; col<values.length;col++){
for(int row=0; row<values.length;row++){
values[row][col] -= colMinValue[col];
}
}
} //End Step 2
/**
* Step 3.1
* Loop through all elements, and run colorNeighbors when the element visited is equal to zero
* */
public void coverZeros(){
numLines = 0;
lines = new int[values.length][values.length];
for(int row=0; row<values.length;row++){
for(int col=0; col<values.length;col++){
if(values[row][col] == 0)
colorNeighbors(row, col, maxVH(row, col));
}
}
}
/**
* Step 3.2
* Checks which direction (vertical,horizontal) contains more zeros, every time a zero is found vertically, we increment the result
* and every time a zero is found horizontally, we decrement the result. At the end, result will be negative, zero or positive
* @param row Row index for the target cell
* @param col Column index for the target cell
* @return Positive integer means that the line passing by indexes [row][col] should be vertical, Zero or Negative means that the line passing by indexes [row][col] should be horizontal
* */
private int maxVH(int row, int col){
int result = 0;
for(int i=0; i<values.length;i++){
if(values[i][col] == 0)
result++;
if(values[row][i] == 0)
result--;
}
return result;
}
/**
* Step 3.3
* Color the neighbors of the cell at index [row][col]. To know which direction to draw the lines, we pass maxVH value.
* @param row Row index for the target cell
* @param col Column index for the target cell
* @param maxVH Value return by the maxVH method, positive means the line to draw passing by indexes [row][col] is vertical, negative or zero means the line to draw passing by indexes [row][col] is horizontal
* */
private void colorNeighbors(int row, int col, int maxVH){
if(lines[row][col] == 2) // if cell is colored twice before (intersection cell), don't color it again
return;
if(maxVH > 0 && lines[row][col] == 1) // if cell colored vertically and needs to be recolored vertically, don't color it again (Allowing this step, will color the same line (result won't change), but the num of line will be incremented (wrong value for the num of line drawn))
return;
if(maxVH <= 0 && lines[row][col] == -1) // if cell colored horizontally and needs to be recolored horizontally, don't color it again (Allowing this step, will color the same line (result won't change), but the num of line will be incremented (wrong value for the num of line drawn))
return;
for(int i=0; i<values.length;i++){ // Loop on cell at indexes [row][col] and its neighbors
if(maxVH > 0) // if value of maxVH is positive, color vertically
lines[i][col] = lines[i][col] == -1 || lines[i][col] == 2 ? 2 : 1; // if cell was colored before as horizontal (-1), and now needs to be colored vertical (1), so this cell is an intersection (2). Else if this value was not colored before, color it vertically
else // if value of maxVH is zero or negative color horizontally
lines[row][i] = lines[row][i] == 1 || lines[row][i] == 2 ? 2 : -1; // if cell was colored before as vertical (1), and now needs to be colored horizontal (-1), so this cell is an intersection (2). Else if this value was not colored before, color it horizontally
}
// increment line number
numLines++;
// printMatrix(lines); // Monitor the line draw steps
}//End step 3
/**
* Step 4
* This step is not always executed. (Check the algorithm in the constructor)
* Create additional zeros, by coloring the minimum value of uncovered cells (cells not colored by any line)
* */
public void createAdditionalZeros(){
int minUncoveredValue = 0; // We don't know the value of the first uncovered cell, so we put a joker value 0 (0 is safe, because before this step, all zeros were covered)
// Find the min in the uncovered numbers
for(int row=0; row<values.length;row++){
for(int col=0; col<values.length;col++){
if(lines[row][col] == 0 && (values[row][col] < minUncoveredValue || minUncoveredValue == 0))
minUncoveredValue = values[row][col];
}
}
// Subtract min form all uncovered elements, and add it to all elements covered twice
for(int row=0; row<values.length;row++){
for(int col=0; col<values.length;col++){
if(lines[row][col] == 0) // If uncovered, subtract
values[row][col] -= minUncoveredValue;
else if(lines[row][col] == 2) // If covered twice, add
values[row][col] += minUncoveredValue;
}
}
} // End step 4
/**
* Optimization, assign every row a cell in a unique column. Since a row can contain more than one zero,
* we need to make sure that all rows are assigned one cell from one unique column. To do this, use brute force
* @param row
* @param boolean If all rows were assigned a cell from a unique column, return true (at the end, guarantee true)
* @return true
* */
private boolean optimization(int row){
if(row == rows.length) // If all rows were assigned a cell
return true;
for(int col=0; col<values.length;col++){ // Try all columns
if(values[row][col] == 0 && occupiedCols[col] == 0){ // If the current cell at column `col` has a value of zero, and the column is not reserved by a previous row
rows[row] = col; // Assign the current row the current column cell
occupiedCols[col] = 1; // Mark the column as reserved
if(optimization(row+1)) // If the next rows were assigned successfully a cell from a unique column, return true
return true;
occupiedCols[col] = 0; // If the next rows were not able to get a cell, go back and try for the previous rows another cell from another column
}
}
return false; // If no cell were assigned for the current row, return false to go back one row to try to assign to it another cell from another column
}
/**
* Overload optimization(int row) method
* @return true
* */
public boolean optimization(){
return optimization(0);
} //End optimization
/**
* Get the result by returning an array containing the cell assigned for each row
* @return Array of rows where each array index represent the row number, and the value at each index is the column assigned to the corresponding row
* */
public int[] getResult(){
return rows;
}
/**
* Get the sum of the value of the assigned cells for all rows using the original passed matrix, and using the rows array to know the index of the column for each row.
* @return Total values
* */
public int getTotal(){
int total = 0;
for(int row = 0; row < values.length; row++)
total += originalValues[row][rows[row]];
return total;
}
/**
* Clone the 2D array
* @return A copy of the 2D array
* */
public int[][] cloneMatrix(int[][] matrix){
int[][] tmp = new int[matrix.length][matrix.length];
for(int row = 0; row < matrix.length; row++){
tmp[row] = matrix[row].clone();
}
return tmp;
}
/**
* Print a 2D array
* @param matrix The target 2D array
* */
public void printMatrix(int[][] matrix){
for(int row=0; row<matrix.length;row++){
for(int col=0; col<matrix.length;col++){
System.out.print(matrix[row][col]+"\t");
}
System.out.println();
}
System.out.println();
}
}