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

167 lines
4.5 KiB

2 years ago
// 注意本文件中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("测试结束");
}
}