From eabd772fabb2ad3406a5320461d6787f0925eb2b Mon Sep 17 00:00:00 2001 From: sivasuthan Date: Wed, 1 Oct 2025 20:36:18 +0530 Subject: [PATCH 1/8] =?UTF-8?q?Bron=E2=80=93Kerbosch=20algorithm=20added.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/thealgorithms/graph/BronKerbosch.java | 119 ++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 src/main/java/com/thealgorithms/graph/BronKerbosch.java diff --git a/src/main/java/com/thealgorithms/graph/BronKerbosch.java b/src/main/java/com/thealgorithms/graph/BronKerbosch.java new file mode 100644 index 000000000000..ede1e59ae0b4 --- /dev/null +++ b/src/main/java/com/thealgorithms/graph/BronKerbosch.java @@ -0,0 +1,119 @@ +package com.thealgorithms.graph; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * Implementation of the Bron–Kerbosch algorithm with pivoting for enumerating all maximal cliques + * in an undirected graph. + * + *

The input graph is represented as an adjacency list where {@code adjacency.get(u)} returns the + * set of vertices adjacent to {@code u}. The algorithm runs in time proportional to the number of + * maximal cliques produced and is widely used for clique enumeration problems.

+ * + * @author Victor French + */ +public final class BronKerbosch { + + private BronKerbosch() { + } + + /** + * Finds all maximal cliques of the provided graph. + * + * @param adjacency adjacency list where {@code adjacency.size()} equals the number of vertices + * @return a list containing every maximal clique, each represented as a {@link Set} of vertices + * @throws IllegalArgumentException if the adjacency list is {@code null}, contains {@code null} + * entries, or references invalid vertices + */ + public static List> findMaximalCliques(List> adjacency) { + if (adjacency == null) { + throw new IllegalArgumentException("Adjacency list must not be null"); + } + + int n = adjacency.size(); + List> graph = new ArrayList<>(n); + for (int u = 0; u < n; u++) { + Set neighbors = adjacency.get(u); + if (neighbors == null) { + throw new IllegalArgumentException("Adjacency list must not contain null sets"); + } + Set copy = new HashSet<>(); + for (int v : neighbors) { + if (v < 0 || v >= n) { + throw new IllegalArgumentException("Neighbor index out of bounds: " + v); + } + if (v != u) { + copy.add(v); + } + } + graph.add(copy); + } + + Set r = new HashSet<>(); + Set p = new HashSet<>(); + Set x = new HashSet<>(); + for (int v = 0; v < n; v++) { + p.add(v); + } + + List> cliques = new ArrayList<>(); + bronKerboschPivot(r, p, x, graph, cliques); + return cliques; + } + + private static void bronKerboschPivot( + Set r, + Set p, + Set x, + List> graph, + List> cliques) { + if (p.isEmpty() && x.isEmpty()) { + cliques.add(new HashSet<>(r)); + return; + } + + int pivot = choosePivot(p, x, graph); + Set candidates = new HashSet<>(p); + if (pivot != -1) { + candidates.removeAll(graph.get(pivot)); + } + + for (Integer v : candidates) { + r.add(v); + Set newP = intersection(p, graph.get(v)); + Set newX = intersection(x, graph.get(v)); + bronKerboschPivot(r, newP, newX, graph, cliques); + r.remove(v); + p.remove(v); + x.add(v); + } + } + + private static int choosePivot(Set p, Set x, List> graph) { + int pivot = -1; + int maxDegree = -1; + Set union = new HashSet<>(p); + union.addAll(x); + for (Integer v : union) { + int degree = graph.get(v).size(); + if (degree > maxDegree) { + maxDegree = degree; + pivot = v; + } + } + return pivot; + } + + private static Set intersection(Set base, Set neighbors) { + Set result = new HashSet<>(); + for (Integer v : base) { + if (neighbors.contains(v)) { + result.add(v); + } + } + return result; + } +} From e55faed6ad5ee5711ec0afc272f7c8752e637eec Mon Sep 17 00:00:00 2001 From: sivasuthan Date: Wed, 1 Oct 2025 20:37:10 +0530 Subject: [PATCH 2/8] =?UTF-8?q?test:Bron=E2=80=93Kerbosch=20algorithm=20ad?= =?UTF-8?q?ded.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../thealgorithms/graph/BronKerboschTest.java | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 src/test/java/com/thealgorithms/graph/BronKerboschTest.java diff --git a/src/test/java/com/thealgorithms/graph/BronKerboschTest.java b/src/test/java/com/thealgorithms/graph/BronKerboschTest.java new file mode 100644 index 000000000000..54c91c6ac1fd --- /dev/null +++ b/src/test/java/com/thealgorithms/graph/BronKerboschTest.java @@ -0,0 +1,79 @@ +package com.thealgorithms.graph; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class BronKerboschTest { + + @Test + @DisplayName("Complete graph returns single clique") + void completeGraph() { + List> adjacency = buildGraph(4); + addUndirectedEdge(adjacency, 0, 1); + addUndirectedEdge(adjacency, 0, 2); + addUndirectedEdge(adjacency, 0, 3); + addUndirectedEdge(adjacency, 1, 2); + addUndirectedEdge(adjacency, 1, 3); + addUndirectedEdge(adjacency, 2, 3); + + List> cliques = BronKerbosch.findMaximalCliques(adjacency); + assertEquals(1, cliques.size()); + assertEquals(Set.of(0, 1, 2, 3), cliques.get(0)); + } + + @Test + @DisplayName("Path graph produces individual edges") + void pathGraph() { + List> adjacency = buildGraph(3); + addUndirectedEdge(adjacency, 0, 1); + addUndirectedEdge(adjacency, 1, 2); + + List> cliques = BronKerbosch.findMaximalCliques(adjacency); + Set> result = new HashSet<>(cliques); + Set> expected = Set.of(Set.of(0, 1), Set.of(1, 2)); + assertEquals(expected, result); + } + + @Test + @DisplayName("Disconnected graph finds cliques per component") + void disconnectedGraph() { + List> adjacency = buildGraph(5); + addUndirectedEdge(adjacency, 0, 1); + addUndirectedEdge(adjacency, 0, 2); + addUndirectedEdge(adjacency, 1, 2); + addUndirectedEdge(adjacency, 3, 4); + + List> cliques = BronKerbosch.findMaximalCliques(adjacency); + Set> result = new HashSet<>(cliques); + Set> expected = Set.of(Set.of(0, 1, 2), Set.of(3, 4)); + assertEquals(expected, result); + } + + @Test + @DisplayName("Null neighbor set triggers exception") + void nullNeighborSet() { + List> adjacency = new ArrayList<>(); + adjacency.add(null); + assertThrows(IllegalArgumentException.class, () -> BronKerbosch.findMaximalCliques(adjacency)); + } + + private static List> buildGraph(int n) { + List> graph = new ArrayList<>(n); + for (int i = 0; i < n; i++) { + graph.add(new HashSet<>()); + } + return graph; + } + + private static void addUndirectedEdge(List> graph, int u, int v) { + graph.get(u).add(v); + graph.get(v).add(u); + } +} From 48f0174a1b115692a9147f8501daaf001a4db276 Mon Sep 17 00:00:00 2001 From: Sivasuthan Sukumar <153247727+Sivasuthan9@users.noreply.github.com> Date: Wed, 1 Oct 2025 22:36:11 +0530 Subject: [PATCH 3/8] lint checked. --- src/test/java/com/thealgorithms/graph/BronKerboschTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/test/java/com/thealgorithms/graph/BronKerboschTest.java b/src/test/java/com/thealgorithms/graph/BronKerboschTest.java index 54c91c6ac1fd..51298f73d851 100644 --- a/src/test/java/com/thealgorithms/graph/BronKerboschTest.java +++ b/src/test/java/com/thealgorithms/graph/BronKerboschTest.java @@ -1,12 +1,12 @@ package com.thealgorithms.graph; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; - import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; From c0ceecf9c819be6e229877c5e106e3a60dcad1b2 Mon Sep 17 00:00:00 2001 From: Sivasuthan Sukumar <153247727+Sivasuthan9@users.noreply.github.com> Date: Wed, 1 Oct 2025 22:43:11 +0530 Subject: [PATCH 4/8] clang-format linting checked. --- src/main/java/com/thealgorithms/graph/BronKerbosch.java | 7 +------ .../java/com/thealgorithms/graph/BronKerboschTest.java | 5 +++-- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/thealgorithms/graph/BronKerbosch.java b/src/main/java/com/thealgorithms/graph/BronKerbosch.java index ede1e59ae0b4..e9a4f6387b84 100644 --- a/src/main/java/com/thealgorithms/graph/BronKerbosch.java +++ b/src/main/java/com/thealgorithms/graph/BronKerbosch.java @@ -64,12 +64,7 @@ public static List> findMaximalCliques(List> adjacency return cliques; } - private static void bronKerboschPivot( - Set r, - Set p, - Set x, - List> graph, - List> cliques) { + private static void bronKerboschPivot(Set r, Set p, Set x, List> graph, List> cliques) { if (p.isEmpty() && x.isEmpty()) { cliques.add(new HashSet<>(r)); return; diff --git a/src/test/java/com/thealgorithms/graph/BronKerboschTest.java b/src/test/java/com/thealgorithms/graph/BronKerboschTest.java index 51298f73d851..d7f463a28f0c 100644 --- a/src/test/java/com/thealgorithms/graph/BronKerboschTest.java +++ b/src/test/java/com/thealgorithms/graph/BronKerboschTest.java @@ -5,11 +5,12 @@ import java.util.List; import java.util.Set; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + class BronKerboschTest { @Test From f10f5fb2b573cc0405787db88107d15e6b148058 Mon Sep 17 00:00:00 2001 From: Sivasuthan Sukumar <153247727+Sivasuthan9@users.noreply.github.com> Date: Wed, 1 Oct 2025 22:57:45 +0530 Subject: [PATCH 5/8] lint checked in remote Removed duplicate import statements for assertions. --- src/test/java/com/thealgorithms/graph/BronKerboschTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/test/java/com/thealgorithms/graph/BronKerboschTest.java b/src/test/java/com/thealgorithms/graph/BronKerboschTest.java index d7f463a28f0c..f8901b9c391f 100644 --- a/src/test/java/com/thealgorithms/graph/BronKerboschTest.java +++ b/src/test/java/com/thealgorithms/graph/BronKerboschTest.java @@ -1,5 +1,8 @@ package com.thealgorithms.graph; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + import java.util.ArrayList; import java.util.HashSet; import java.util.List; @@ -8,9 +11,6 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; - class BronKerboschTest { @Test From 914bbee0858bbe4074940062031c6929bf0b4204 Mon Sep 17 00:00:00 2001 From: Sivasuthan Sukumar <153247727+Sivasuthan9@users.noreply.github.com> Date: Wed, 1 Oct 2025 23:08:54 +0530 Subject: [PATCH 6/8] Remove unnecessary blank line in BronKerboschTest --- src/test/java/com/thealgorithms/graph/BronKerboschTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/test/java/com/thealgorithms/graph/BronKerboschTest.java b/src/test/java/com/thealgorithms/graph/BronKerboschTest.java index f8901b9c391f..54c91c6ac1fd 100644 --- a/src/test/java/com/thealgorithms/graph/BronKerboschTest.java +++ b/src/test/java/com/thealgorithms/graph/BronKerboschTest.java @@ -7,7 +7,6 @@ import java.util.HashSet; import java.util.List; import java.util.Set; - import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; From ee4ee224da326616dd8cd744a74af7d2c44316ff Mon Sep 17 00:00:00 2001 From: sivasuthan Date: Wed, 1 Oct 2025 23:20:23 +0530 Subject: [PATCH 7/8] EdmondsKarp algorithm added. --- .../com/thealgorithms/graph/BronKerbosch.java | 2 +- .../com/thealgorithms/graph/EdmondsKarp.java | 107 ++++++++++++++++++ .../thealgorithms/graph/EdmondsKarpTest.java | 67 +++++++++++ 3 files changed, 175 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/thealgorithms/graph/EdmondsKarp.java create mode 100644 src/test/java/com/thealgorithms/graph/EdmondsKarpTest.java diff --git a/src/main/java/com/thealgorithms/graph/BronKerbosch.java b/src/main/java/com/thealgorithms/graph/BronKerbosch.java index e9a4f6387b84..0510d9bcf494 100644 --- a/src/main/java/com/thealgorithms/graph/BronKerbosch.java +++ b/src/main/java/com/thealgorithms/graph/BronKerbosch.java @@ -13,7 +13,7 @@ * set of vertices adjacent to {@code u}. The algorithm runs in time proportional to the number of * maximal cliques produced and is widely used for clique enumeration problems.

* - * @author Victor French + * @author Wikipedia: Bron–Kerbosch algorithm */ public final class BronKerbosch { diff --git a/src/main/java/com/thealgorithms/graph/EdmondsKarp.java b/src/main/java/com/thealgorithms/graph/EdmondsKarp.java new file mode 100644 index 000000000000..59e7b09cb49c --- /dev/null +++ b/src/main/java/com/thealgorithms/graph/EdmondsKarp.java @@ -0,0 +1,107 @@ +package com.thealgorithms.graph; + +import java.util.ArrayDeque; +import java.util.Arrays; +import java.util.Queue; + +/** + * Implementation of the Edmonds–Karp algorithm for computing the maximum flow of a directed graph. + *

+ * The algorithm runs in O(V * E^2) time and is a specific implementation of the Ford–Fulkerson + * method where the augmenting paths are found using breadth-first search (BFS) to ensure the + * shortest augmenting paths (in terms of the number of edges) are used. + *

+ * + *

The graph is represented with a capacity matrix where {@code capacity[u][v]} denotes the + * capacity of the edge from {@code u} to {@code v}. Negative capacities are not allowed.

+ * + * @author Wikipedia: EdmondsKarp algorithm + */ +public final class EdmondsKarp { + + private EdmondsKarp() { + } + + /** + * Computes the maximum flow from {@code source} to {@code sink} in the provided capacity matrix. + * + * @param capacity the capacity matrix representing the directed graph; must be square and non-null + * @param source the source vertex index + * @param sink the sink vertex index + * @return the value of the maximum flow between {@code source} and {@code sink} + * @throws IllegalArgumentException if the matrix is {@code null}, not square, contains negative + * capacities, or if {@code source} / {@code sink} indices are invalid + */ + public static int maxFlow(int[][] capacity, int source, int sink) { + if (capacity == null || capacity.length == 0) { + throw new IllegalArgumentException("Capacity matrix must not be null or empty"); + } + + final int n = capacity.length; + for (int row = 0; row < n; row++) { + if (capacity[row] == null || capacity[row].length != n) { + throw new IllegalArgumentException("Capacity matrix must be square"); + } + for (int col = 0; col < n; col++) { + if (capacity[row][col] < 0) { + throw new IllegalArgumentException("Capacities must be non-negative"); + } + } + } + + if (source < 0 || source >= n || sink < 0 || sink >= n) { + throw new IllegalArgumentException("Source and sink must be valid vertex indices"); + } + if (source == sink) { + return 0; + } + + final int[][] residual = new int[n][n]; + for (int i = 0; i < n; i++) { + residual[i] = Arrays.copyOf(capacity[i], n); + } + + final 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; + } + + return maxFlow; + } + + private static boolean bfs(int[][] residual, int source, int sink, int[] parent) { + Arrays.fill(parent, -1); + parent[source] = source; + + Queue queue = new ArrayDeque<>(); + queue.add(source); + + while (!queue.isEmpty()) { + int u = queue.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; + } + queue.add(v); + } + } + } + return false; + } +} diff --git a/src/test/java/com/thealgorithms/graph/EdmondsKarpTest.java b/src/test/java/com/thealgorithms/graph/EdmondsKarpTest.java new file mode 100644 index 000000000000..82f29caf17ba --- /dev/null +++ b/src/test/java/com/thealgorithms/graph/EdmondsKarpTest.java @@ -0,0 +1,67 @@ +package com.thealgorithms.graph; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class EdmondsKarpTest { + + @Test + @DisplayName("Classic CLRS network yields max flow 23") + void clrsExample() { + int[][] capacity = { + {0, 16, 13, 0, 0, 0}, + {0, 0, 10, 12, 0, 0}, + {0, 4, 0, 0, 14, 0}, + {0, 0, 9, 0, 0, 20}, + {0, 0, 0, 7, 0, 4}, + {0, 0, 0, 0, 0, 0} + }; + int maxFlow = EdmondsKarp.maxFlow(capacity, 0, 5); + assertEquals(23, maxFlow); + } + + @Test + @DisplayName("Disconnected network has zero flow") + void disconnectedGraph() { + int[][] capacity = { + {0, 0, 0}, + {0, 0, 0}, + {0, 0, 0} + }; + int maxFlow = EdmondsKarp.maxFlow(capacity, 0, 2); + assertEquals(0, maxFlow); + } + + @Test + @DisplayName("Source equals sink returns zero") + void sourceEqualsSink() { + int[][] capacity = { + {0, 5}, + {0, 0} + }; + int maxFlow = EdmondsKarp.maxFlow(capacity, 0, 0); + assertEquals(0, maxFlow); + } + + @Test + @DisplayName("Invalid matrix throws exception") + void invalidMatrix() { + int[][] capacity = { + {0, 1}, + {1} + }; + assertThrows(IllegalArgumentException.class, () -> EdmondsKarp.maxFlow(capacity, 0, 1)); + } + + @Test + @DisplayName("Negative capacity is rejected") + void negativeCapacity() { + int[][] capacity = { + {0, -1}, + {0, 0} + }; + assertThrows(IllegalArgumentException.class, () -> EdmondsKarp.maxFlow(capacity, 0, 1)); + } +} From d318759f1ce8e63e6b49e892b2777b7002c2d619 Mon Sep 17 00:00:00 2001 From: Sivasuthan Sukumar <153247727+Sivasuthan9@users.noreply.github.com> Date: Wed, 1 Oct 2025 23:26:45 +0530 Subject: [PATCH 8/8] reformatted --- .../thealgorithms/graph/EdmondsKarpTest.java | 31 ++++--------------- 1 file changed, 6 insertions(+), 25 deletions(-) diff --git a/src/test/java/com/thealgorithms/graph/EdmondsKarpTest.java b/src/test/java/com/thealgorithms/graph/EdmondsKarpTest.java index 82f29caf17ba..55aeda381031 100644 --- a/src/test/java/com/thealgorithms/graph/EdmondsKarpTest.java +++ b/src/test/java/com/thealgorithms/graph/EdmondsKarpTest.java @@ -2,6 +2,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; + import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -10,14 +11,7 @@ class EdmondsKarpTest { @Test @DisplayName("Classic CLRS network yields max flow 23") void clrsExample() { - int[][] capacity = { - {0, 16, 13, 0, 0, 0}, - {0, 0, 10, 12, 0, 0}, - {0, 4, 0, 0, 14, 0}, - {0, 0, 9, 0, 0, 20}, - {0, 0, 0, 7, 0, 4}, - {0, 0, 0, 0, 0, 0} - }; + int[][] capacity = {{0, 16, 13, 0, 0, 0}, {0, 0, 10, 12, 0, 0}, {0, 4, 0, 0, 14, 0}, {0, 0, 9, 0, 0, 20}, {0, 0, 0, 7, 0, 4}, {0, 0, 0, 0, 0, 0}}; int maxFlow = EdmondsKarp.maxFlow(capacity, 0, 5); assertEquals(23, maxFlow); } @@ -25,11 +19,7 @@ void clrsExample() { @Test @DisplayName("Disconnected network has zero flow") void disconnectedGraph() { - int[][] capacity = { - {0, 0, 0}, - {0, 0, 0}, - {0, 0, 0} - }; + int[][] capacity = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}}; int maxFlow = EdmondsKarp.maxFlow(capacity, 0, 2); assertEquals(0, maxFlow); } @@ -37,10 +27,7 @@ void disconnectedGraph() { @Test @DisplayName("Source equals sink returns zero") void sourceEqualsSink() { - int[][] capacity = { - {0, 5}, - {0, 0} - }; + int[][] capacity = {{0, 5}, {0, 0}}; int maxFlow = EdmondsKarp.maxFlow(capacity, 0, 0); assertEquals(0, maxFlow); } @@ -48,20 +35,14 @@ void sourceEqualsSink() { @Test @DisplayName("Invalid matrix throws exception") void invalidMatrix() { - int[][] capacity = { - {0, 1}, - {1} - }; + int[][] capacity = {{0, 1}, {1}}; assertThrows(IllegalArgumentException.class, () -> EdmondsKarp.maxFlow(capacity, 0, 1)); } @Test @DisplayName("Negative capacity is rejected") void negativeCapacity() { - int[][] capacity = { - {0, -1}, - {0, 0} - }; + int[][] capacity = {{0, -1}, {0, 0}}; assertThrows(IllegalArgumentException.class, () -> EdmondsKarp.maxFlow(capacity, 0, 1)); } }