|
|
|
|
// 注意本文件中,graph不是邻接矩阵的含义,而是一个二部图
|
|
|
|
|
// 在长度为N的邻接矩阵matrix中,所有的点有N个,matrix[i][j]表示点i到点j的距离或者权重
|
|
|
|
|
// 而在二部图graph中,所有的点有2*N个,行所对应的点有N个,列所对应的点有N个
|
|
|
|
|
// 而且认为,行所对应的点之间是没有路径的,列所对应的点之间也是没有路径的!
|
|
|
|
|
|
|
|
|
|
package class_2022_03_5_week;
|
|
|
|
|
|
|
|
|
|
import java.util.Arrays;
|
|
|
|
|
|
|
|
|
|
// km算法
|
|
|
|
|
// O(N^3),最大匹配问题,最优的解!
|
|
|
|
|
public class Code01_KMAlgorithm {
|
|
|
|
|
|
|
|
|
|
// 暴力解
|
|
|
|
|
public static int right(int[][] graph) {
|
|
|
|
|
int N = graph.length;
|
|
|
|
|
int[] to = new int[N];
|
|
|
|
|
for (int i = 0; i < N; i++) {
|
|
|
|
|
to[i] = 1;
|
|
|
|
|
}
|
|
|
|
|
return process(0, to, graph);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static int process(int from, int[] to, int[][] graph) {
|
|
|
|
|
if (from == graph.length) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
int ans = 0;
|
|
|
|
|
for (int i = 0; i < to.length; i++) {
|
|
|
|
|
if (to[i] == 1) {
|
|
|
|
|
to[i] = 0;
|
|
|
|
|
ans = Math.max(ans, graph[from][i] + process(from + 1, to, graph));
|
|
|
|
|
to[i] = 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return ans;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static int km(int[][] graph) {
|
|
|
|
|
int N = graph.length;
|
|
|
|
|
int[] match = new int[N];
|
|
|
|
|
int[] lx = new int[N];
|
|
|
|
|
int[] ly = new int[N];
|
|
|
|
|
// dfs过程中,碰过的点!
|
|
|
|
|
boolean[] x = new boolean[N];
|
|
|
|
|
boolean[] y = new boolean[N];
|
|
|
|
|
// 降低的预期!
|
|
|
|
|
// 公主上,打一个,降低预期的值,只维持最小!
|
|
|
|
|
int[] slack = new int[N];
|
|
|
|
|
int invalid = Integer.MAX_VALUE;
|
|
|
|
|
for (int i = 0; i < N; i++) {
|
|
|
|
|
match[i] = -1;
|
|
|
|
|
lx[i] = -invalid;
|
|
|
|
|
for (int j = 0; j < N; j++) {
|
|
|
|
|
lx[i] = Math.max(lx[i], graph[i][j]);
|
|
|
|
|
}
|
|
|
|
|
ly[i] = 0;
|
|
|
|
|
}
|
|
|
|
|
for (int from = 0; from < N; from++) {
|
|
|
|
|
for (int i = 0; i < N; i++) {
|
|
|
|
|
slack[i] = invalid;
|
|
|
|
|
}
|
|
|
|
|
Arrays.fill(x, false);
|
|
|
|
|
Arrays.fill(y, false);
|
|
|
|
|
// dfs() : from王子,能不能不降预期,匹配成功!
|
|
|
|
|
// 能:dfs返回true!
|
|
|
|
|
// 不能:dfs返回false!
|
|
|
|
|
while (!dfs(from, x, y, lx, ly, match, slack, graph)) {
|
|
|
|
|
// 刚才的dfs,失败了!
|
|
|
|
|
// 需要拿到,公主的slack里面,预期下降幅度的最小值!
|
|
|
|
|
int d = invalid;
|
|
|
|
|
for (int i = 0; i < N; i++) {
|
|
|
|
|
if (!y[i] && slack[i] < d) {
|
|
|
|
|
d = slack[i];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// 按照最小预期来调整预期
|
|
|
|
|
for (int i = 0; i < N; i++) {
|
|
|
|
|
if (x[i]) {
|
|
|
|
|
lx[i] = lx[i] - d;
|
|
|
|
|
}
|
|
|
|
|
if (y[i]) {
|
|
|
|
|
ly[i] = ly[i] + d;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Arrays.fill(x, false);
|
|
|
|
|
Arrays.fill(y, false);
|
|
|
|
|
// 然后回到while里,再次尝试
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
int ans = 0;
|
|
|
|
|
for (int i = 0; i < N; i++) {
|
|
|
|
|
ans += (lx[i] + ly[i]);
|
|
|
|
|
}
|
|
|
|
|
return ans;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// from, 当前的王子
|
|
|
|
|
// x,王子碰没碰过
|
|
|
|
|
// y, 公主碰没碰过
|
|
|
|
|
// lx,所有王子的预期
|
|
|
|
|
// ly, 所有公主的预期
|
|
|
|
|
// match,所有公主,之前的分配,之前的爷们!
|
|
|
|
|
// slack,连过,但没允许的公主,最小下降的幅度
|
|
|
|
|
// map,报价,所有王子对公主的报价
|
|
|
|
|
// 返回,from号王子,不降预期能不能配成!
|
|
|
|
|
public static boolean dfs(int from, boolean[] x, boolean[] y, int[] lx, int[] ly, int[] match, int[] slack,
|
|
|
|
|
int[][] map) {
|
|
|
|
|
int N = map.length;
|
|
|
|
|
x[from] = true;
|
|
|
|
|
for (int to = 0; to < N; to++) {
|
|
|
|
|
if (!y[to]) { // 只有没dfs过的公主,才会去尝试
|
|
|
|
|
int d = lx[from] + ly[to] - map[from][to];
|
|
|
|
|
if (d != 0) {// 如果当前的路不符合预期,更新公主的slack值
|
|
|
|
|
slack[to] = Math.min(slack[to], d);
|
|
|
|
|
} else { // 如果当前的路符合预期,尝试直接拿下,或者抢夺让之前的安排倒腾去
|
|
|
|
|
y[to] = true;
|
|
|
|
|
if (match[to] == -1 || dfs(match[to], x, y, lx, ly, match, slack, map)) {
|
|
|
|
|
match[to] = from;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 为了测试
|
|
|
|
|
public static int[][] randomGraph(int N, int V) {
|
|
|
|
|
int[][] graph = new int[N][N];
|
|
|
|
|
for (int i = 0; i < N; i++) {
|
|
|
|
|
for (int j = i + 1; j < N; j++) {
|
|
|
|
|
int num = (int) (Math.random() * V);
|
|
|
|
|
graph[i][j] = num;
|
|
|
|
|
graph[j][i] = num;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return graph;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 为了测试
|
|
|
|
|
public static void main(String[] args) {
|
|
|
|
|
int N = 10;
|
|
|
|
|
int V = 20;
|
|
|
|
|
int testTime = 100;
|
|
|
|
|
System.out.println("测试开始");
|
|
|
|
|
for (int i = 0; i < testTime; i++) {
|
|
|
|
|
int[][] graph = randomGraph(N, V);
|
|
|
|
|
int ans1 = right(graph);
|
|
|
|
|
int ans2 = km(graph);
|
|
|
|
|
if (ans1 != ans2) {
|
|
|
|
|
System.out.println("Oops!");
|
|
|
|
|
for (int r = 0; r < graph.length; r++) {
|
|
|
|
|
for (int c = 0; c < graph.length; c++) {
|
|
|
|
|
System.out.print(graph[r][c] + " ");
|
|
|
|
|
}
|
|
|
|
|
System.out.println();
|
|
|
|
|
}
|
|
|
|
|
System.out.println(ans1);
|
|
|
|
|
System.out.println(ans2);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
System.out.println("测试结束");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|