You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

142 lines
3.3 KiB
Java

import java.util.*;
// https://en.wikipedia.org/wiki/Hungarian_algorithm in O(n^2 * m)
public class Hungarian {
// a[n][m], n <= m, sum(a[i][p[i]] -> min
public static int minWeightPerfectMatching(int[][] a) {
int n = a.length - 1;
int m = a[0].length - 1;
int[] u = new int[n + 1];
int[] v = new int[m + 1];
int[] p = new int[m + 1];
int[] way = new int[m + 1];
for (int i = 1; i <= n; ++i) {
int[] minv = new int[m + 1];
Arrays.fill(minv, Integer.MAX_VALUE);
boolean[] used = new boolean[m + 1];
p[0] = i;
int j0 = 0;
while (p[j0] != 0) {
used[j0] = true;
int i0 = p[j0];
int delta = Integer.MAX_VALUE;
int j1 = 0;
for (int j = 1; j <= m; ++j)
if (!used[j]) {
int d = a[i0][j] - u[i0] - v[j];
if (minv[j] > d) {
minv[j] = d;
way[j] = j0;
}
if (delta > minv[j]) {
delta = minv[j];
j1 = j;
}
}
for (int j = 0; j <= m; ++j)
if (used[j]) {
u[p[j]] += delta;
v[j] -= delta;
} else
minv[j] -= delta;
j0 = j1;
}
while (j0 != 0) {
int j1 = way[j0];
p[j0] = p[j1];
j0 = j1;
}
}
int[] matching = new int[n + 1];
for (int i = 1; i <= m; ++i)
matching[p[i]] = i;
return -v[0];
}
// random test
public static void main(String[] args) {
Random rnd = new Random(1);
for (int step = 0; step < 1000; step++) {
int max = 9;
int n = rnd.nextInt(max) + 1;
int m = n + rnd.nextInt(max + 1 - n);
int[][] a = new int[n + 1][m + 1];
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
a[i][j] = rnd.nextInt(100_000) - 50_000;
}
}
int res1 = minWeightPerfectMatching(a);
int res2 = minWeightPerfectMatchingSlow(a);
if (res1 != res2) {
System.err.println(res1 + " " + res2);
}
}
}
static int minWeightPerfectMatchingSlow(int[][] a) {
int n = a.length - 1;
int m = a[0].length - 1;
int[] dp = new int[1 << (n + m)];
Arrays.fill(dp, Integer.MAX_VALUE / 2);
dp[0] = 0;
int res = Integer.MAX_VALUE;
for (int mask = 0; mask < dp.length; mask++) {
for (int i = 0; i < n; i++) {
if ((mask & (1 << i)) == 0) {
for (int j = 0; j < m; j++) {
if ((mask & (1 << (j + n))) == 0) {
dp[mask | (1 << i) | (1 << (j + n))] = Math.min(dp[mask | (1 << i) | (1 << (j + n))], dp[mask] + a[i + 1][j + 1]);
if (Integer.bitCount(mask & ((1 << n) - 1)) == n - 1)
res = Math.min(res, dp[mask | (1 << i) | (1 << (j + n))]);
}
}
break;
}
}
}
return res;
}
static int minimumWeightPerfectMatchingSlow2(int[][] a) {
int n = a.length - 1;
int m = a[0].length - 1;
int res = Integer.MAX_VALUE;
int[] p = new int[n];
for (int i = 0; i < n; i++)
p[i] = i;
do {
int cur = 0;
for (int i = 0; i < p.length; i++)
cur += a[i + 1][p[i] + 1];
res = Math.min(res, cur);
} while (nextArrangement(p, m));
return res;
}
static boolean nextArrangement(int[] p, int n) {
boolean[] used = new boolean[n];
for (int x : p) {
used[x] = true;
}
int m = p.length;
for (int i = m - 1; i >= 0; i--) {
used[p[i]] = false;
for (int j = p[i] + 1; j < n; j++) {
if (!used[j]) {
p[i++] = j;
used[j] = true;
for (int k = 0; k < n && i < m; k++) {
if (!used[k]) {
p[i++] = k;
}
}
return true;
}
}
}
return false;
}
}