Skip to content

Commit f8f315e

Browse files
Sivasuthan9alxkm
andauthored
Extend Graph Algorithms: Added Two Popular Algorithms: BronKerbosch, EdmondsKarp (#6576)
* Bron–Kerbosch algorithm added. * test:Bron–Kerbosch algorithm added. * lint checked. * clang-format linting checked. * lint checked in remote Removed duplicate import statements for assertions. * Remove unnecessary blank line in BronKerboschTest * EdmondsKarp algorithm added. * reformatted --------- Co-authored-by: Oleksandr Klymenko <[email protected]>
1 parent 05ceb19 commit f8f315e

File tree

4 files changed

+348
-0
lines changed

4 files changed

+348
-0
lines changed
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
package com.thealgorithms.graph;
2+
3+
import java.util.ArrayList;
4+
import java.util.HashSet;
5+
import java.util.List;
6+
import java.util.Set;
7+
8+
/**
9+
* Implementation of the Bron–Kerbosch algorithm with pivoting for enumerating all maximal cliques
10+
* in an undirected graph.
11+
*
12+
* <p>The input graph is represented as an adjacency list where {@code adjacency.get(u)} returns the
13+
* set of vertices adjacent to {@code u}. The algorithm runs in time proportional to the number of
14+
* maximal cliques produced and is widely used for clique enumeration problems.</p>
15+
*
16+
* @author <a href="https://en.wikipedia.org/wiki/Bron%E2%80%93Kerbosch_algorithm">Wikipedia: Bron–Kerbosch algorithm</a>
17+
*/
18+
public final class BronKerbosch {
19+
20+
private BronKerbosch() {
21+
}
22+
23+
/**
24+
* Finds all maximal cliques of the provided graph.
25+
*
26+
* @param adjacency adjacency list where {@code adjacency.size()} equals the number of vertices
27+
* @return a list containing every maximal clique, each represented as a {@link Set} of vertices
28+
* @throws IllegalArgumentException if the adjacency list is {@code null}, contains {@code null}
29+
* entries, or references invalid vertices
30+
*/
31+
public static List<Set<Integer>> findMaximalCliques(List<Set<Integer>> adjacency) {
32+
if (adjacency == null) {
33+
throw new IllegalArgumentException("Adjacency list must not be null");
34+
}
35+
36+
int n = adjacency.size();
37+
List<Set<Integer>> graph = new ArrayList<>(n);
38+
for (int u = 0; u < n; u++) {
39+
Set<Integer> neighbors = adjacency.get(u);
40+
if (neighbors == null) {
41+
throw new IllegalArgumentException("Adjacency list must not contain null sets");
42+
}
43+
Set<Integer> copy = new HashSet<>();
44+
for (int v : neighbors) {
45+
if (v < 0 || v >= n) {
46+
throw new IllegalArgumentException("Neighbor index out of bounds: " + v);
47+
}
48+
if (v != u) {
49+
copy.add(v);
50+
}
51+
}
52+
graph.add(copy);
53+
}
54+
55+
Set<Integer> r = new HashSet<>();
56+
Set<Integer> p = new HashSet<>();
57+
Set<Integer> x = new HashSet<>();
58+
for (int v = 0; v < n; v++) {
59+
p.add(v);
60+
}
61+
62+
List<Set<Integer>> cliques = new ArrayList<>();
63+
bronKerboschPivot(r, p, x, graph, cliques);
64+
return cliques;
65+
}
66+
67+
private static void bronKerboschPivot(Set<Integer> r, Set<Integer> p, Set<Integer> x, List<Set<Integer>> graph, List<Set<Integer>> cliques) {
68+
if (p.isEmpty() && x.isEmpty()) {
69+
cliques.add(new HashSet<>(r));
70+
return;
71+
}
72+
73+
int pivot = choosePivot(p, x, graph);
74+
Set<Integer> candidates = new HashSet<>(p);
75+
if (pivot != -1) {
76+
candidates.removeAll(graph.get(pivot));
77+
}
78+
79+
for (Integer v : candidates) {
80+
r.add(v);
81+
Set<Integer> newP = intersection(p, graph.get(v));
82+
Set<Integer> newX = intersection(x, graph.get(v));
83+
bronKerboschPivot(r, newP, newX, graph, cliques);
84+
r.remove(v);
85+
p.remove(v);
86+
x.add(v);
87+
}
88+
}
89+
90+
private static int choosePivot(Set<Integer> p, Set<Integer> x, List<Set<Integer>> graph) {
91+
int pivot = -1;
92+
int maxDegree = -1;
93+
Set<Integer> union = new HashSet<>(p);
94+
union.addAll(x);
95+
for (Integer v : union) {
96+
int degree = graph.get(v).size();
97+
if (degree > maxDegree) {
98+
maxDegree = degree;
99+
pivot = v;
100+
}
101+
}
102+
return pivot;
103+
}
104+
105+
private static Set<Integer> intersection(Set<Integer> base, Set<Integer> neighbors) {
106+
Set<Integer> result = new HashSet<>();
107+
for (Integer v : base) {
108+
if (neighbors.contains(v)) {
109+
result.add(v);
110+
}
111+
}
112+
return result;
113+
}
114+
}
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
package com.thealgorithms.graph;
2+
3+
import java.util.ArrayDeque;
4+
import java.util.Arrays;
5+
import java.util.Queue;
6+
7+
/**
8+
* Implementation of the Edmonds–Karp algorithm for computing the maximum flow of a directed graph.
9+
* <p>
10+
* The algorithm runs in O(V * E^2) time and is a specific implementation of the Ford–Fulkerson
11+
* method where the augmenting paths are found using breadth-first search (BFS) to ensure the
12+
* shortest augmenting paths (in terms of the number of edges) are used.
13+
* </p>
14+
*
15+
* <p>The graph is represented with a capacity matrix where {@code capacity[u][v]} denotes the
16+
* capacity of the edge from {@code u} to {@code v}. Negative capacities are not allowed.</p>
17+
*
18+
* @author <a href="https://en.wikipedia.org/wiki/Edmonds%E2%80%93Karp_algorithm">Wikipedia: EdmondsKarp algorithm</a>
19+
*/
20+
public final class EdmondsKarp {
21+
22+
private EdmondsKarp() {
23+
}
24+
25+
/**
26+
* Computes the maximum flow from {@code source} to {@code sink} in the provided capacity matrix.
27+
*
28+
* @param capacity the capacity matrix representing the directed graph; must be square and non-null
29+
* @param source the source vertex index
30+
* @param sink the sink vertex index
31+
* @return the value of the maximum flow between {@code source} and {@code sink}
32+
* @throws IllegalArgumentException if the matrix is {@code null}, not square, contains negative
33+
* capacities, or if {@code source} / {@code sink} indices are invalid
34+
*/
35+
public static int maxFlow(int[][] capacity, int source, int sink) {
36+
if (capacity == null || capacity.length == 0) {
37+
throw new IllegalArgumentException("Capacity matrix must not be null or empty");
38+
}
39+
40+
final int n = capacity.length;
41+
for (int row = 0; row < n; row++) {
42+
if (capacity[row] == null || capacity[row].length != n) {
43+
throw new IllegalArgumentException("Capacity matrix must be square");
44+
}
45+
for (int col = 0; col < n; col++) {
46+
if (capacity[row][col] < 0) {
47+
throw new IllegalArgumentException("Capacities must be non-negative");
48+
}
49+
}
50+
}
51+
52+
if (source < 0 || source >= n || sink < 0 || sink >= n) {
53+
throw new IllegalArgumentException("Source and sink must be valid vertex indices");
54+
}
55+
if (source == sink) {
56+
return 0;
57+
}
58+
59+
final int[][] residual = new int[n][n];
60+
for (int i = 0; i < n; i++) {
61+
residual[i] = Arrays.copyOf(capacity[i], n);
62+
}
63+
64+
final int[] parent = new int[n];
65+
int maxFlow = 0;
66+
67+
while (bfs(residual, source, sink, parent)) {
68+
int pathFlow = Integer.MAX_VALUE;
69+
for (int v = sink; v != source; v = parent[v]) {
70+
int u = parent[v];
71+
pathFlow = Math.min(pathFlow, residual[u][v]);
72+
}
73+
74+
for (int v = sink; v != source; v = parent[v]) {
75+
int u = parent[v];
76+
residual[u][v] -= pathFlow;
77+
residual[v][u] += pathFlow;
78+
}
79+
80+
maxFlow += pathFlow;
81+
}
82+
83+
return maxFlow;
84+
}
85+
86+
private static boolean bfs(int[][] residual, int source, int sink, int[] parent) {
87+
Arrays.fill(parent, -1);
88+
parent[source] = source;
89+
90+
Queue<Integer> queue = new ArrayDeque<>();
91+
queue.add(source);
92+
93+
while (!queue.isEmpty()) {
94+
int u = queue.poll();
95+
for (int v = 0; v < residual.length; v++) {
96+
if (residual[u][v] > 0 && parent[v] == -1) {
97+
parent[v] = u;
98+
if (v == sink) {
99+
return true;
100+
}
101+
queue.add(v);
102+
}
103+
}
104+
}
105+
return false;
106+
}
107+
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package com.thealgorithms.graph;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertThrows;
5+
6+
import java.util.ArrayList;
7+
import java.util.HashSet;
8+
import java.util.List;
9+
import java.util.Set;
10+
import org.junit.jupiter.api.DisplayName;
11+
import org.junit.jupiter.api.Test;
12+
13+
class BronKerboschTest {
14+
15+
@Test
16+
@DisplayName("Complete graph returns single clique")
17+
void completeGraph() {
18+
List<Set<Integer>> adjacency = buildGraph(4);
19+
addUndirectedEdge(adjacency, 0, 1);
20+
addUndirectedEdge(adjacency, 0, 2);
21+
addUndirectedEdge(adjacency, 0, 3);
22+
addUndirectedEdge(adjacency, 1, 2);
23+
addUndirectedEdge(adjacency, 1, 3);
24+
addUndirectedEdge(adjacency, 2, 3);
25+
26+
List<Set<Integer>> cliques = BronKerbosch.findMaximalCliques(adjacency);
27+
assertEquals(1, cliques.size());
28+
assertEquals(Set.of(0, 1, 2, 3), cliques.get(0));
29+
}
30+
31+
@Test
32+
@DisplayName("Path graph produces individual edges")
33+
void pathGraph() {
34+
List<Set<Integer>> adjacency = buildGraph(3);
35+
addUndirectedEdge(adjacency, 0, 1);
36+
addUndirectedEdge(adjacency, 1, 2);
37+
38+
List<Set<Integer>> cliques = BronKerbosch.findMaximalCliques(adjacency);
39+
Set<Set<Integer>> result = new HashSet<>(cliques);
40+
Set<Set<Integer>> expected = Set.of(Set.of(0, 1), Set.of(1, 2));
41+
assertEquals(expected, result);
42+
}
43+
44+
@Test
45+
@DisplayName("Disconnected graph finds cliques per component")
46+
void disconnectedGraph() {
47+
List<Set<Integer>> adjacency = buildGraph(5);
48+
addUndirectedEdge(adjacency, 0, 1);
49+
addUndirectedEdge(adjacency, 0, 2);
50+
addUndirectedEdge(adjacency, 1, 2);
51+
addUndirectedEdge(adjacency, 3, 4);
52+
53+
List<Set<Integer>> cliques = BronKerbosch.findMaximalCliques(adjacency);
54+
Set<Set<Integer>> result = new HashSet<>(cliques);
55+
Set<Set<Integer>> expected = Set.of(Set.of(0, 1, 2), Set.of(3, 4));
56+
assertEquals(expected, result);
57+
}
58+
59+
@Test
60+
@DisplayName("Null neighbor set triggers exception")
61+
void nullNeighborSet() {
62+
List<Set<Integer>> adjacency = new ArrayList<>();
63+
adjacency.add(null);
64+
assertThrows(IllegalArgumentException.class, () -> BronKerbosch.findMaximalCliques(adjacency));
65+
}
66+
67+
private static List<Set<Integer>> buildGraph(int n) {
68+
List<Set<Integer>> graph = new ArrayList<>(n);
69+
for (int i = 0; i < n; i++) {
70+
graph.add(new HashSet<>());
71+
}
72+
return graph;
73+
}
74+
75+
private static void addUndirectedEdge(List<Set<Integer>> graph, int u, int v) {
76+
graph.get(u).add(v);
77+
graph.get(v).add(u);
78+
}
79+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package com.thealgorithms.graph;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertThrows;
5+
6+
import org.junit.jupiter.api.DisplayName;
7+
import org.junit.jupiter.api.Test;
8+
9+
class EdmondsKarpTest {
10+
11+
@Test
12+
@DisplayName("Classic CLRS network yields max flow 23")
13+
void clrsExample() {
14+
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}};
15+
int maxFlow = EdmondsKarp.maxFlow(capacity, 0, 5);
16+
assertEquals(23, maxFlow);
17+
}
18+
19+
@Test
20+
@DisplayName("Disconnected network has zero flow")
21+
void disconnectedGraph() {
22+
int[][] capacity = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}};
23+
int maxFlow = EdmondsKarp.maxFlow(capacity, 0, 2);
24+
assertEquals(0, maxFlow);
25+
}
26+
27+
@Test
28+
@DisplayName("Source equals sink returns zero")
29+
void sourceEqualsSink() {
30+
int[][] capacity = {{0, 5}, {0, 0}};
31+
int maxFlow = EdmondsKarp.maxFlow(capacity, 0, 0);
32+
assertEquals(0, maxFlow);
33+
}
34+
35+
@Test
36+
@DisplayName("Invalid matrix throws exception")
37+
void invalidMatrix() {
38+
int[][] capacity = {{0, 1}, {1}};
39+
assertThrows(IllegalArgumentException.class, () -> EdmondsKarp.maxFlow(capacity, 0, 1));
40+
}
41+
42+
@Test
43+
@DisplayName("Negative capacity is rejected")
44+
void negativeCapacity() {
45+
int[][] capacity = {{0, -1}, {0, 0}};
46+
assertThrows(IllegalArgumentException.class, () -> EdmondsKarp.maxFlow(capacity, 0, 1));
47+
}
48+
}

0 commit comments

Comments
 (0)