Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 114 additions & 0 deletions src/main/java/com/thealgorithms/graph/BronKerbosch.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
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.
*
* <p>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.</p>
*
* @author <a href="https://en.wikipedia.org/wiki/Bron%E2%80%93Kerbosch_algorithm">Wikipedia: Bron–Kerbosch algorithm</a>
*/
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<Set<Integer>> findMaximalCliques(List<Set<Integer>> adjacency) {
if (adjacency == null) {
throw new IllegalArgumentException("Adjacency list must not be null");
}

int n = adjacency.size();
List<Set<Integer>> graph = new ArrayList<>(n);
for (int u = 0; u < n; u++) {
Set<Integer> neighbors = adjacency.get(u);
if (neighbors == null) {
throw new IllegalArgumentException("Adjacency list must not contain null sets");
}
Set<Integer> 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<Integer> r = new HashSet<>();
Set<Integer> p = new HashSet<>();
Set<Integer> x = new HashSet<>();
for (int v = 0; v < n; v++) {
p.add(v);
}

List<Set<Integer>> cliques = new ArrayList<>();
bronKerboschPivot(r, p, x, graph, cliques);
return cliques;
}

private static void bronKerboschPivot(Set<Integer> r, Set<Integer> p, Set<Integer> x, List<Set<Integer>> graph, List<Set<Integer>> cliques) {
if (p.isEmpty() && x.isEmpty()) {
cliques.add(new HashSet<>(r));
return;
}

int pivot = choosePivot(p, x, graph);
Set<Integer> candidates = new HashSet<>(p);
if (pivot != -1) {
candidates.removeAll(graph.get(pivot));
}

for (Integer v : candidates) {
r.add(v);
Set<Integer> newP = intersection(p, graph.get(v));
Set<Integer> 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<Integer> p, Set<Integer> x, List<Set<Integer>> graph) {
int pivot = -1;
int maxDegree = -1;
Set<Integer> 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<Integer> intersection(Set<Integer> base, Set<Integer> neighbors) {
Set<Integer> result = new HashSet<>();
for (Integer v : base) {
if (neighbors.contains(v)) {
result.add(v);
}
}
return result;
}
}
107 changes: 107 additions & 0 deletions src/main/java/com/thealgorithms/graph/EdmondsKarp.java
Original file line number Diff line number Diff line change
@@ -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.
* <p>
* 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.
* </p>
*
* <p>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.</p>
*
* @author <a href="https://en.wikipedia.org/wiki/Edmonds%E2%80%93Karp_algorithm">Wikipedia: EdmondsKarp algorithm</a>
*/
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<Integer> 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;
}
}
79 changes: 79 additions & 0 deletions src/test/java/com/thealgorithms/graph/BronKerboschTest.java
Original file line number Diff line number Diff line change
@@ -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<Set<Integer>> 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<Set<Integer>> 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<Set<Integer>> adjacency = buildGraph(3);
addUndirectedEdge(adjacency, 0, 1);
addUndirectedEdge(adjacency, 1, 2);

List<Set<Integer>> cliques = BronKerbosch.findMaximalCliques(adjacency);
Set<Set<Integer>> result = new HashSet<>(cliques);
Set<Set<Integer>> 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<Set<Integer>> adjacency = buildGraph(5);
addUndirectedEdge(adjacency, 0, 1);
addUndirectedEdge(adjacency, 0, 2);
addUndirectedEdge(adjacency, 1, 2);
addUndirectedEdge(adjacency, 3, 4);

List<Set<Integer>> cliques = BronKerbosch.findMaximalCliques(adjacency);
Set<Set<Integer>> result = new HashSet<>(cliques);
Set<Set<Integer>> 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<Set<Integer>> adjacency = new ArrayList<>();
adjacency.add(null);
assertThrows(IllegalArgumentException.class, () -> BronKerbosch.findMaximalCliques(adjacency));
}

private static List<Set<Integer>> buildGraph(int n) {
List<Set<Integer>> graph = new ArrayList<>(n);
for (int i = 0; i < n; i++) {
graph.add(new HashSet<>());
}
return graph;
}

private static void addUndirectedEdge(List<Set<Integer>> graph, int u, int v) {
graph.get(u).add(v);
graph.get(v).add(u);
}
}
48 changes: 48 additions & 0 deletions src/test/java/com/thealgorithms/graph/EdmondsKarpTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
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));
}
}