Skip to content

Commit 6e9aa61

Browse files
committed
Add Gomory–Hu Tree (all-pairs min-cuts via n1 max-flows)
1 parent 4858ec9 commit 6e9aa61

File tree

3 files changed

+265
-0
lines changed

3 files changed

+265
-0
lines changed

DIRECTORY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,7 @@
371371
- 📄 [Dinic](src/main/java/com/thealgorithms/graph/Dinic.java)
372372
- 📄 [Edmonds](src/main/java/com/thealgorithms/graph/Edmonds.java)
373373
- 📄 [EdmondsKarp](src/main/java/com/thealgorithms/graph/EdmondsKarp.java)
374+
- 📄 [GomoryHuTree](src/main/java/com/thealgorithms/graph/GomoryHuTree.java)
374375
- 📄 [HopcroftKarp](src/main/java/com/thealgorithms/graph/HopcroftKarp.java)
375376
- 📄 [HungarianAlgorithm](src/main/java/com/thealgorithms/graph/HungarianAlgorithm.java)
376377
- 📄 [PredecessorConstrainedDfs](src/main/java/com/thealgorithms/graph/PredecessorConstrainedDfs.java)
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
package com.thealgorithms.graph;
2+
3+
import java.util.ArrayDeque;
4+
import java.util.Arrays;
5+
import java.util.Queue;
6+
7+
/**
8+
* Gomory–Hu tree construction for undirected graphs via n−1 max-flow computations.
9+
*
10+
* <p>API: {@code buildTree(int[][])} returns {@code {parent, weight}} arrays for the tree.
11+
*
12+
* @see <a href="https://en.wikipedia.org/wiki/Gomory%E2%80%93Hu_tree">Wikipedia: Gomory–Hu tree</a>
13+
*/
14+
15+
public final class GomoryHuTree {
16+
private GomoryHuTree() {
17+
}
18+
19+
public static int[][] buildTree(int[][] cap) {
20+
validateCapacityMatrix(cap);
21+
final int n = cap.length;
22+
if (n == 1) {
23+
return new int[][] {new int[] {-1}, new int[] {0}};
24+
}
25+
26+
int[] parent = new int[n];
27+
int[] weight = new int[n];
28+
Arrays.fill(parent, 0);
29+
parent[0] = -1;
30+
weight[0] = 0;
31+
32+
for (int s = 1; s < n; s++) {
33+
int t = parent[s];
34+
MaxFlowResult res = edmondsKarpWithMinCut(cap, s, t);
35+
int f = res.flow;
36+
weight[s] = f;
37+
38+
for (int v = 0; v < n; v++) {
39+
if (v != s && parent[v] == t && res.reachable[v]) {
40+
parent[v] = s;
41+
}
42+
}
43+
44+
if (t != 0 && res.reachable[parent[t]]) {
45+
parent[s] = parent[t];
46+
parent[t] = s;
47+
weight[s] = weight[t];
48+
weight[t] = f;
49+
}
50+
}
51+
return new int[][] {parent, weight};
52+
}
53+
54+
private static void validateCapacityMatrix(int[][] cap) {
55+
if (cap == null || cap.length == 0) {
56+
throw new IllegalArgumentException("Capacity matrix must not be null or empty");
57+
}
58+
final int n = cap.length;
59+
for (int i = 0; i < n; i++) {
60+
if (cap[i] == null || cap[i].length != n) {
61+
throw new IllegalArgumentException("Capacity matrix must be square");
62+
}
63+
for (int j = 0; j < n; j++) {
64+
if (cap[i][j] < 0) {
65+
throw new IllegalArgumentException("Capacities must be non-negative");
66+
}
67+
}
68+
}
69+
}
70+
71+
private static final class MaxFlowResult {
72+
final int flow;
73+
final boolean[] reachable;
74+
MaxFlowResult(int flow, boolean[] reachable) {
75+
this.flow = flow;
76+
this.reachable = reachable;
77+
}
78+
}
79+
80+
private static MaxFlowResult edmondsKarpWithMinCut(int[][] capacity, int source, int sink) {
81+
final int n = capacity.length;
82+
int[][] residual = new int[n][n];
83+
for (int i = 0; i < n; i++) {
84+
residual[i] = Arrays.copyOf(capacity[i], n);
85+
}
86+
87+
int[] parent = new int[n];
88+
int maxFlow = 0;
89+
90+
while (bfs(residual, source, sink, parent)) {
91+
int pathFlow = Integer.MAX_VALUE;
92+
for (int v = sink; v != source; v = parent[v]) {
93+
int u = parent[v];
94+
pathFlow = Math.min(pathFlow, residual[u][v]);
95+
}
96+
for (int v = sink; v != source; v = parent[v]) {
97+
int u = parent[v];
98+
residual[u][v] -= pathFlow;
99+
residual[v][u] += pathFlow;
100+
}
101+
maxFlow += pathFlow;
102+
}
103+
104+
boolean[] reachable = new boolean[n];
105+
markReachable(residual, source, reachable);
106+
return new MaxFlowResult(maxFlow, reachable);
107+
}
108+
109+
private static boolean bfs(int[][] residual, int source, int sink, int[] parent) {
110+
Arrays.fill(parent, -1);
111+
parent[source] = source;
112+
Queue<Integer> q = new ArrayDeque<>();
113+
q.add(source);
114+
while (!q.isEmpty()) {
115+
int u = q.poll();
116+
for (int v = 0; v < residual.length; v++) {
117+
if (residual[u][v] > 0 && parent[v] == -1) {
118+
parent[v] = u;
119+
if (v == sink) {
120+
return true;
121+
}
122+
q.add(v);
123+
}
124+
}
125+
}
126+
return false;
127+
}
128+
129+
private static void markReachable(int[][] residual, int source, boolean[] vis) {
130+
Arrays.fill(vis, false);
131+
Queue<Integer> q = new ArrayDeque<>();
132+
vis[source] = true;
133+
q.add(source);
134+
while (!q.isEmpty()) {
135+
int u = q.poll();
136+
for (int v = 0; v < residual.length; v++) {
137+
if (!vis[v] && residual[u][v] > 0) {
138+
vis[v] = true;
139+
q.add(v);
140+
}
141+
}
142+
}
143+
}
144+
}
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
package com.thealgorithms.graph;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
5+
import java.util.ArrayDeque;
6+
import java.util.ArrayList;
7+
import java.util.Arrays;
8+
import java.util.List;
9+
import java.util.Queue;
10+
import java.util.Random;
11+
import org.junit.jupiter.api.DisplayName;
12+
import org.junit.jupiter.api.Test;
13+
14+
class GomoryHuTreeTest {
15+
16+
@Test
17+
@DisplayName("Single node graph")
18+
void singleNode() {
19+
int[][] cap = {{0}};
20+
int[][] res = GomoryHuTree.buildTree(cap);
21+
int[] parent = res[0];
22+
int[] weight = res[1];
23+
assertEquals(-1, parent[0]);
24+
assertEquals(0, weight[0]);
25+
}
26+
27+
@Test
28+
@DisplayName("Triangle undirected graph with known min-cuts")
29+
void triangleGraph() {
30+
// 0-1:3, 1-2:2, 0-2:4
31+
int[][] cap = new int[3][3];
32+
cap[0][1] = cap[1][0] = 3;
33+
cap[1][2] = cap[2][1] = 2;
34+
cap[0][2] = cap[2][0] = 4;
35+
36+
int[][] tree = GomoryHuTree.buildTree(cap);
37+
// validate all pairs via path-min-edge equals maxflow
38+
validateAllPairs(cap, tree);
39+
}
40+
41+
@Test
42+
@DisplayName("Random small undirected graphs compare to EdmondsKarp")
43+
void randomSmallGraphs() {
44+
Random rng = new Random(42);
45+
for (int n = 2; n <= 6; n++) {
46+
for (int iter = 0; iter < 10; iter++) {
47+
int[][] cap = randSymmetricMatrix(n, 0, 5, rng);
48+
int[][] tree = GomoryHuTree.buildTree(cap);
49+
validateAllPairs(cap, tree);
50+
}
51+
}
52+
}
53+
54+
private static int[][] randSymmetricMatrix(int n, int lo, int hi, Random rng) {
55+
int[][] a = new int[n][n];
56+
for (int i = 0; i < n; i++) {
57+
for (int j = i + 1; j < n; j++) {
58+
int w = rng.nextInt(hi - lo + 1) + lo;
59+
a[i][j] = a[j][i] = w;
60+
}
61+
}
62+
// zero diagonal
63+
for (int i = 0; i < n; i++) a[i][i] = 0;
64+
return a;
65+
}
66+
67+
private static void validateAllPairs(int[][] cap, int[][] tree) {
68+
int n = cap.length;
69+
int[] parent = tree[0];
70+
int[] weight = tree[1];
71+
72+
// build adjacency list of tree without generic array creation
73+
List<List<int[]>> g = new ArrayList<>();
74+
for (int i = 0; i < n; i++) g.add(new ArrayList<>());
75+
for (int v = 1; v < n; v++) {
76+
int u = parent[v];
77+
int w = weight[v];
78+
g.get(u).add(new int[] {v, w});
79+
g.get(v).add(new int[] {u, w});
80+
}
81+
82+
for (int s = 0; s < n; s++) {
83+
for (int t = s + 1; t < n; t++) {
84+
int treeVal = minEdgeOnPath(g, s, t);
85+
int flowVal = EdmondsKarp.maxFlow(cap, s, t);
86+
assertEquals(flowVal, treeVal, "pair (" + s + "," + t + ")");
87+
}
88+
}
89+
}
90+
91+
private static int minEdgeOnPath(List<List<int[]>> g, int s, int t) {
92+
// BFS to record parent and edge weight along the path, since it's a tree, unique path exists
93+
int n = g.size();
94+
int[] parent = new int[n];
95+
int[] edgeW = new int[n];
96+
Arrays.fill(parent, -1);
97+
Queue<Integer> q = new ArrayDeque<>();
98+
q.add(s);
99+
parent[s] = s;
100+
while (!q.isEmpty()) {
101+
int u = q.poll();
102+
if (u == t) break;
103+
for (int[] e : g.get(u)) {
104+
int v = e[0], w = e[1];
105+
if (parent[v] == -1) {
106+
parent[v] = u;
107+
edgeW[v] = w;
108+
q.add(v);
109+
}
110+
}
111+
}
112+
int cur = t;
113+
int ans = Integer.MAX_VALUE;
114+
while (cur != s) {
115+
ans = Math.min(ans, edgeW[cur]);
116+
cur = parent[cur];
117+
}
118+
return ans == Integer.MAX_VALUE ? 0 : ans;
119+
}
120+
}

0 commit comments

Comments
 (0)