diff --git a/DIRECTORY.md b/DIRECTORY.md index 47833a3f59f2..93794673f0fc 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -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) diff --git a/src/main/java/com/thealgorithms/graph/GomoryHuTree.java b/src/main/java/com/thealgorithms/graph/GomoryHuTree.java new file mode 100644 index 000000000000..f8c110f25571 --- /dev/null +++ b/src/main/java/com/thealgorithms/graph/GomoryHuTree.java @@ -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. + * + *
API: {@code buildTree(int[][])} returns {@code {parent, weight}} arrays for the tree.
+ *
+ * @see Wikipedia: GomoryโHu tree
+ */
+
+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> 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
> 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