mirror of
https://github.com/TheAlgorithms/Java.git
synced 2026-03-13 08:40:43 +08:00
feat: Added Gomory–Hu Tree (all-pairs min-cuts via n1 max-flows) (#6818)
* Add Gomory–Hu Tree (all-pairs min-cuts via n1 max-flows) * Stabilize Monte Carlo integration with antithetic variates to reduce variance * Fix Checkstyle in GomoryHuTreeTest: remove inner assignments, add braces, split declarations * SpotBugs: use RandomGenerator interface in test helper --------- Co-authored-by: Deniz Altunkapan <deniz.altunkapan@outlook.com>
This commit is contained in:
144
src/main/java/com/thealgorithms/graph/GomoryHuTree.java
Normal file
144
src/main/java/com/thealgorithms/graph/GomoryHuTree.java
Normal file
@@ -0,0 +1,144 @@
|
||||
package com.thealgorithms.graph;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Arrays;
|
||||
import java.util.Queue;
|
||||
|
||||
/**
|
||||
* Gomory–Hu tree construction for undirected graphs via n−1 max-flow computations.
|
||||
*
|
||||
* <p>API: {@code buildTree(int[][])} returns {@code {parent, weight}} arrays for the tree.
|
||||
*
|
||||
* @see <a href="https://en.wikipedia.org/wiki/Gomory%E2%80%93Hu_tree">Wikipedia: Gomory–Hu tree</a>
|
||||
*/
|
||||
|
||||
public final class GomoryHuTree {
|
||||
private GomoryHuTree() {
|
||||
}
|
||||
|
||||
public static int[][] buildTree(int[][] cap) {
|
||||
validateCapacityMatrix(cap);
|
||||
final int n = cap.length;
|
||||
if (n == 1) {
|
||||
return new int[][] {new int[] {-1}, new int[] {0}};
|
||||
}
|
||||
|
||||
int[] parent = new int[n];
|
||||
int[] weight = new int[n];
|
||||
Arrays.fill(parent, 0);
|
||||
parent[0] = -1;
|
||||
weight[0] = 0;
|
||||
|
||||
for (int s = 1; s < n; s++) {
|
||||
int t = parent[s];
|
||||
MaxFlowResult res = edmondsKarpWithMinCut(cap, s, t);
|
||||
int f = res.flow;
|
||||
weight[s] = f;
|
||||
|
||||
for (int v = 0; v < n; v++) {
|
||||
if (v != s && parent[v] == t && res.reachable[v]) {
|
||||
parent[v] = s;
|
||||
}
|
||||
}
|
||||
|
||||
if (t != 0 && res.reachable[parent[t]]) {
|
||||
parent[s] = parent[t];
|
||||
parent[t] = s;
|
||||
weight[s] = weight[t];
|
||||
weight[t] = f;
|
||||
}
|
||||
}
|
||||
return new int[][] {parent, weight};
|
||||
}
|
||||
|
||||
private static void validateCapacityMatrix(int[][] cap) {
|
||||
if (cap == null || cap.length == 0) {
|
||||
throw new IllegalArgumentException("Capacity matrix must not be null or empty");
|
||||
}
|
||||
final int n = cap.length;
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (cap[i] == null || cap[i].length != n) {
|
||||
throw new IllegalArgumentException("Capacity matrix must be square");
|
||||
}
|
||||
for (int j = 0; j < n; j++) {
|
||||
if (cap[i][j] < 0) {
|
||||
throw new IllegalArgumentException("Capacities must be non-negative");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final class MaxFlowResult {
|
||||
final int flow;
|
||||
final boolean[] reachable;
|
||||
MaxFlowResult(int flow, boolean[] reachable) {
|
||||
this.flow = flow;
|
||||
this.reachable = reachable;
|
||||
}
|
||||
}
|
||||
|
||||
private static MaxFlowResult edmondsKarpWithMinCut(int[][] capacity, int source, int sink) {
|
||||
final int n = capacity.length;
|
||||
int[][] residual = new int[n][n];
|
||||
for (int i = 0; i < n; i++) {
|
||||
residual[i] = Arrays.copyOf(capacity[i], n);
|
||||
}
|
||||
|
||||
int[] parent = new int[n];
|
||||
int maxFlow = 0;
|
||||
|
||||
while (bfs(residual, source, sink, parent)) {
|
||||
int pathFlow = Integer.MAX_VALUE;
|
||||
for (int v = sink; v != source; v = parent[v]) {
|
||||
int u = parent[v];
|
||||
pathFlow = Math.min(pathFlow, residual[u][v]);
|
||||
}
|
||||
for (int v = sink; v != source; v = parent[v]) {
|
||||
int u = parent[v];
|
||||
residual[u][v] -= pathFlow;
|
||||
residual[v][u] += pathFlow;
|
||||
}
|
||||
maxFlow += pathFlow;
|
||||
}
|
||||
|
||||
boolean[] reachable = new boolean[n];
|
||||
markReachable(residual, source, reachable);
|
||||
return new MaxFlowResult(maxFlow, reachable);
|
||||
}
|
||||
|
||||
private static boolean bfs(int[][] residual, int source, int sink, int[] parent) {
|
||||
Arrays.fill(parent, -1);
|
||||
parent[source] = source;
|
||||
Queue<Integer> q = new ArrayDeque<>();
|
||||
q.add(source);
|
||||
while (!q.isEmpty()) {
|
||||
int u = q.poll();
|
||||
for (int v = 0; v < residual.length; v++) {
|
||||
if (residual[u][v] > 0 && parent[v] == -1) {
|
||||
parent[v] = u;
|
||||
if (v == sink) {
|
||||
return true;
|
||||
}
|
||||
q.add(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void markReachable(int[][] residual, int source, boolean[] vis) {
|
||||
Arrays.fill(vis, false);
|
||||
Queue<Integer> q = new ArrayDeque<>();
|
||||
vis[source] = true;
|
||||
q.add(source);
|
||||
while (!q.isEmpty()) {
|
||||
int u = q.poll();
|
||||
for (int v = 0; v < residual.length; v++) {
|
||||
if (!vis[v] && residual[u][v] > 0) {
|
||||
vis[v] = true;
|
||||
q.add(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -64,13 +64,21 @@ public final class MonteCarloIntegration {
|
||||
if (!validate(fx, a, b, n)) {
|
||||
throw new IllegalArgumentException("Invalid input parameters");
|
||||
}
|
||||
double totalArea = 0.0;
|
||||
double total = 0.0;
|
||||
double interval = b - a;
|
||||
for (int i = 0; i < n; i++) {
|
||||
double x = a + generator.nextDouble() * interval;
|
||||
totalArea += fx.apply(x);
|
||||
int pairs = n / 2;
|
||||
for (int i = 0; i < pairs; i++) {
|
||||
double u = generator.nextDouble();
|
||||
double x1 = a + u * interval;
|
||||
double x2 = a + (1.0 - u) * interval;
|
||||
total += fx.apply(x1);
|
||||
total += fx.apply(x2);
|
||||
}
|
||||
return interval * totalArea / n;
|
||||
if ((n & 1) == 1) {
|
||||
double x = a + generator.nextDouble() * interval;
|
||||
total += fx.apply(x);
|
||||
}
|
||||
return interval * total / n;
|
||||
}
|
||||
|
||||
private static boolean validate(Function<Double, Double> fx, double a, double b, int n) {
|
||||
|
||||
Reference in New Issue
Block a user