Skip to content
Open
Show file tree
Hide file tree
Changes from all 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;
}
}