mirror of
https://github.com/TheAlgorithms/Java.git
synced 2025-12-19 07:00:35 +08:00
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 <alexanderklmn@gmail.com>
This commit is contained in:
committed by
GitHub
parent
05ceb192c9
commit
f8f315eaa8
114
src/main/java/com/thealgorithms/graph/BronKerbosch.java
Normal file
114
src/main/java/com/thealgorithms/graph/BronKerbosch.java
Normal file
@@ -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
src/main/java/com/thealgorithms/graph/EdmondsKarp.java
Normal file
107
src/main/java/com/thealgorithms/graph/EdmondsKarp.java
Normal file
@@ -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
src/test/java/com/thealgorithms/graph/BronKerboschTest.java
Normal file
79
src/test/java/com/thealgorithms/graph/BronKerboschTest.java
Normal file
@@ -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
src/test/java/com/thealgorithms/graph/EdmondsKarpTest.java
Normal file
48
src/test/java/com/thealgorithms/graph/EdmondsKarpTest.java
Normal file
@@ -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));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user