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 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 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); + } + } + } + } +} diff --git a/src/main/java/com/thealgorithms/randomized/MonteCarloIntegration.java b/src/main/java/com/thealgorithms/randomized/MonteCarloIntegration.java index 05d7abbbcd6c..06101295e880 100644 --- a/src/main/java/com/thealgorithms/randomized/MonteCarloIntegration.java +++ b/src/main/java/com/thealgorithms/randomized/MonteCarloIntegration.java @@ -64,13 +64,21 @@ private static double doApproximate(Function 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 fx, double a, double b, int n) { diff --git a/src/test/java/com/thealgorithms/graph/GomoryHuTreeTest.java b/src/test/java/com/thealgorithms/graph/GomoryHuTreeTest.java new file mode 100644 index 000000000000..241f23c0fa1d --- /dev/null +++ b/src/test/java/com/thealgorithms/graph/GomoryHuTreeTest.java @@ -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> 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 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; + } +}