Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions DIRECTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,7 @@
- 📄 [Dinic](src/main/java/com/thealgorithms/graph/Dinic.java)
- 📄 [Edmonds](src/main/java/com/thealgorithms/graph/Edmonds.java)
- 📄 [EdmondsKarp](src/main/java/com/thealgorithms/graph/EdmondsKarp.java)
- 📄 [GomoryHuTree](src/main/java/com/thealgorithms/graph/GomoryHuTree.java)
- 📄 [HopcroftKarp](src/main/java/com/thealgorithms/graph/HopcroftKarp.java)
- 📄 [HungarianAlgorithm](src/main/java/com/thealgorithms/graph/HungarianAlgorithm.java)
- 📄 [PredecessorConstrainedDfs](src/main/java/com/thealgorithms/graph/PredecessorConstrainedDfs.java)
Expand Down
144 changes: 144 additions & 0 deletions src/main/java/com/thealgorithms/graph/GomoryHuTree.java
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,21 @@ private static double doApproximate(Function<Double, Double> fx, double a, doubl
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++) {
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);
}
if ((n & 1) == 1) {
double x = a + generator.nextDouble() * interval;
totalArea += fx.apply(x);
total += fx.apply(x);
}
return interval * totalArea / n;
return interval * total / n;
}

private static boolean validate(Function<Double, Double> fx, double a, double b, int n) {
Expand Down
132 changes: 132 additions & 0 deletions src/test/java/com/thealgorithms/graph/GomoryHuTreeTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
package com.thealgorithms.graph;

import static org.junit.jupiter.api.Assertions.assertEquals;

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Queue;
import java.util.Random;
import java.util.random.RandomGenerator;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

class GomoryHuTreeTest {

@Test
@DisplayName("Single node graph")
void singleNode() {
int[][] cap = {{0}};
int[][] res = GomoryHuTree.buildTree(cap);
int[] parent = res[0];
int[] weight = res[1];
assertEquals(-1, parent[0]);
assertEquals(0, weight[0]);
}

@Test
@DisplayName("Triangle undirected graph with known min-cuts")
void triangleGraph() {
// 0-1:3, 1-2:2, 0-2:4
int[][] cap = new int[3][3];
cap[0][1] = 3;
cap[1][0] = 3;
cap[1][2] = 2;
cap[2][1] = 2;
cap[0][2] = 4;
cap[2][0] = 4;

int[][] tree = GomoryHuTree.buildTree(cap);
// validate all pairs via path-min-edge equals maxflow
validateAllPairs(cap, tree);
}

@Test
@DisplayName("Random small undirected graphs compare to EdmondsKarp")
void randomSmallGraphs() {
Random rng = new Random(42);
for (int n = 2; n <= 6; n++) {
for (int iter = 0; iter < 10; iter++) {
int[][] cap = randSymmetricMatrix(n, 0, 5, rng);
int[][] tree = GomoryHuTree.buildTree(cap);
validateAllPairs(cap, tree);
}
}
}

private static int[][] randSymmetricMatrix(int n, int lo, int hi, RandomGenerator rng) {
int[][] a = new int[n][n];
for (int i = 0; i < n; i++) {
for (int j = i + 1; j < n; j++) {
int w = rng.nextInt(hi - lo + 1) + lo;
a[i][j] = w;
a[j][i] = w;
}
}
// zero diagonal
for (int i = 0; i < n; i++) {
a[i][i] = 0;
}
return a;
}

private static void validateAllPairs(int[][] cap, int[][] tree) {
int n = cap.length;
int[] parent = tree[0];
int[] weight = tree[1];

// build adjacency list of tree without generic array creation
List<List<int[]>> g = new ArrayList<>();
for (int i = 0; i < n; i++) {
g.add(new ArrayList<>());
}
for (int v = 1; v < n; v++) {
int u = parent[v];
int w = weight[v];
g.get(u).add(new int[] {v, w});
g.get(v).add(new int[] {u, w});
}

for (int s = 0; s < n; s++) {
for (int t = s + 1; t < n; t++) {
int treeVal = minEdgeOnPath(g, s, t);
int flowVal = EdmondsKarp.maxFlow(cap, s, t);
assertEquals(flowVal, treeVal, "pair (" + s + "," + t + ")");
}
}
}

private static int minEdgeOnPath(List<List<int[]>> g, int s, int t) {
// BFS to record parent and edge weight along the path, since it's a tree, unique path exists
int n = g.size();
int[] parent = new int[n];
int[] edgeW = new int[n];
Arrays.fill(parent, -1);
Queue<Integer> q = new ArrayDeque<>();
q.add(s);
parent[s] = s;
while (!q.isEmpty()) {
int u = q.poll();
if (u == t) {
break;
}
for (int[] e : g.get(u)) {
int v = e[0];
int w = e[1];
if (parent[v] == -1) {
parent[v] = u;
edgeW[v] = w;
q.add(v);
}
}
}
int cur = t;
int ans = Integer.MAX_VALUE;
while (cur != s) {
ans = Math.min(ans, edgeW[cur]);
cur = parent[cur];
}
return ans == Integer.MAX_VALUE ? 0 : ans;
}
}