diff --git a/src/main/java/com/williamfiset/algorithms/graphtheory/FindAllCliques.java b/src/main/java/com/williamfiset/algorithms/graphtheory/FindAllCliques.java new file mode 100644 index 000000000..00c0a8e59 --- /dev/null +++ b/src/main/java/com/williamfiset/algorithms/graphtheory/FindAllCliques.java @@ -0,0 +1,104 @@ +package com.williamfiset.algorithms.graphtheory; + +/** + * Finds all cliques (complete sub-graphs) in a given graph. The algorithm loops and finds all + * cliques for each given clique size up until the size of the graph itself. + * + *
Complexity: O(n^2) + */ +public class FindAllCliques { + static int size = 10000; + static int[] vertices = new int[size]; + static int n; + static int[][] graph = new int[size][size]; + static int[] degree = new int[size]; + + /** + * Function that checks if the given set of vertices in store array is a clique or not + * + * @param count number of vertices in the vertices array + * @return true id the current subgraph is a clique false otherwise + */ + static boolean isClique(int count) { + for (int i = 1; i < count; i++) { + for (int j = i + 1; j < count; j++) { + // for missing edges + if (graph[vertices[i]][vertices[j]] == 0) { + return false; + } + } + } + return true; + } + + /** + * Function that prints the clique + * + * @param n number of nodes in the clique + */ + static void print(int n) { + for (int i = 1; i < n; i++) { + if (i < n - 1) { + System.out.print(vertices[i] + ", "); + } else { + System.out.println(vertices[i]); + } + } + } + + /** + * Function that finds all the cliques of size s + * + * @param i initial index counter + * @param currentSize the size of the current sub-graph + * @param size the vertex count of the searched clique + */ + static void findCliques(int i, int currentSize, int size) { + // Find insertable vertices + for (int j = i + 1; j <= n - (size - currentSize); j++) { + if (degree[j] >= size - 1) { + vertices[currentSize] = j; + // Max subgraph size achieved + if (isClique(currentSize + 1)) { + // Max subgraph size not yet achieved + if (currentSize < size) { + findCliques(j, currentSize + 1, size); + } else { + print(currentSize + 1); + } + } + } + } + } + + /** + * Function that finds all cliques + * + * @param edges the list of edges formed with the nodes of the graph + * @param nodesNr the number of nodes of the graph + */ + public static void findAllCliques(int[][] edges, int nodesNr) { + n = nodesNr; + for (int[] edge : edges) { + graph[edge[0]][edge[1]] = 1; + graph[edge[1]][edge[0]] = 1; + degree[edge[0]]++; + degree[edge[1]]++; + } + for (int i = 1; i <= n; i++) { + findCliques(0, 1, i); + } + } + + public static void main(String[] args) { + int[][] edges = { + {1, 2}, + {2, 3}, + {3, 1}, + {4, 3}, + {4, 5}, + {5, 3}, + }; + findAllCliques(edges, 5); + } +} diff --git a/src/test/java/com/williamfiset/algorithms/graphtheory/FindAllCliquesTest.java b/src/test/java/com/williamfiset/algorithms/graphtheory/FindAllCliquesTest.java new file mode 100644 index 000000000..70d6c679c --- /dev/null +++ b/src/test/java/com/williamfiset/algorithms/graphtheory/FindAllCliquesTest.java @@ -0,0 +1,108 @@ +package com.williamfiset.algorithms.graphtheory; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +public class FindAllCliquesTest { + private final ByteArrayOutputStream outContent = new ByteArrayOutputStream(); + private final ByteArrayOutputStream errContent = new ByteArrayOutputStream(); + private final PrintStream originalOut = System.out; + private final PrintStream originalErr = System.err; + + private String outputBuilder(String[] output) { + String lineSeparator = System.getProperty("line.separator"); + StringBuilder result = new StringBuilder(); + for (String element : output) { + result.append(element).append(lineSeparator); + } + + return result.toString(); + } + + @Before + public void setUpStreams() { + System.setOut(new PrintStream(outContent)); + System.setErr(new PrintStream(errContent)); + } + + @After + public void restoreStreams() { + System.setOut(originalOut); + System.setErr(originalErr); + } + + @Test + public void testFindAllCliquesEmptyGraph() { + int[][] edges = {}; + String[] cliquesList = {}; + + FindAllCliques.findAllCliques(edges, 0); + Assert.assertEquals(outputBuilder(cliquesList), outContent.toString()); + } + + @Test + public void testFindAllCliquesSingleNode() { + int[][] edges = {}; + String[] cliquesList = {"1"}; + + FindAllCliques.findAllCliques(edges, 1); + Assert.assertEquals(outputBuilder(cliquesList), outContent.toString()); + } + + @Test + public void testFindAllCliquesMultipleNodes() { + int[][] edges = { + {1, 2}, + {2, 3}, + {3, 1}, + {4, 3}, + {4, 5}, + {5, 3}, + }; + + String[] cliquesList = { + "1", "2", "3", "4", "5", "1, 2", "1, 3", "2, 3", "3, 4", "3, 5", "4, 5", "1, 2, 3", "3, 4, 5", + }; + + FindAllCliques.findAllCliques(edges, 5); + + Assert.assertEquals(outputBuilder(cliquesList), outContent.toString()); + } + + @Test(expected = NullPointerException.class) + public void testFindAllCliquesNullInput() { + FindAllCliques.findAllCliques(null, 0); + } + + @Test(expected = IndexOutOfBoundsException.class) + public void testFindAllCliquesTooManyNodes() { + int[][] edges = { + {1, 2}, + {2, 3}, + {3, 1}, + {4, 3}, + {4, 5}, + {5, 3}, + }; + FindAllCliques.findAllCliques(edges, 10001); + } + + @Test + public void testFindAllCliquesNegativeNumberOfNodes() { + int[][] edges = { + {1, 2}, + {2, 3}, + {3, 1}, + {4, 3}, + {4, 5}, + {5, 3}, + }; + FindAllCliques.findAllCliques(edges, -1); + + Assert.assertEquals("", outContent.toString()); + } +}