package uk.ac.rhul.cs.graph;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import uk.ac.rhul.cs.utils.IntegerRange;
/**
* Finds all the maximal cliques in a graph using the Bron-Kerbosch algorithm.
*
* This implementation is based on the implementation found in the JGraphT
* library, but it is adapted to our {@link Graph} data type and uses more
* efficient data structures.
*
* @author ntamas
*/
public class BronKerboschMaximalCliqueFinder extends GraphAlgorithm {
/**
* Internal method for finding maximal cliques.
*
* @param result a collection in which the cliques are collected
* @param potentialClique a potential clique being built during the
* algorithm
* @param candidates candidate vertices with which we can extend the
* potential clique
* @param alreadyFound the set of vertices already found
*/
protected void findCliques(Collection<List<Integer>> result,
HashSet<Integer> potentialClique,
HashSet<Integer> candidates,
HashSet<Integer> alreadyFound) {
if (isAnyConnectedToAllCandidates(alreadyFound, candidates))
return;
Graph graph = this.getGraph();
Iterator<Integer> it = candidates.iterator();
while (it.hasNext()) {
Integer candidate = it.next();
// create newCandidates and newAlreadyFound
HashSet<Integer> newCandidates = new HashSet<Integer>();
HashSet<Integer> newAlreadyFound = new HashSet<Integer>();
// move candidate node to potentialClique
potentialClique.add(candidate);
it.remove();
// create newCandidates by removing nodes in candidates that are
// not connected to the candidate node
int[] neis = graph.getAdjacentNodeIndicesArray(candidate, Directedness.ALL);
for (int nei: neis) {
if (candidates.contains(nei))
newCandidates.add(nei);
if (alreadyFound.contains(nei))
newAlreadyFound.add(nei);
}
if (newCandidates.isEmpty() && newAlreadyFound.isEmpty()) {
// this is a maximal clique
result.add(new ArrayList<Integer>(potentialClique));
} else {
// recursive call
findCliques(result, potentialClique, newCandidates, newAlreadyFound);
}
alreadyFound.add(candidate);
potentialClique.remove(candidate);
}
}
/**
* Finds all maximal cliques and stores them in the given list.
*
* @param result the collection in which the result will be stored
*/
public void collectMaximalCliques(Collection<List<Integer>> result) {
HashSet<Integer> potentialClique = new HashSet<Integer>();
HashSet<Integer> candidates = new HashSet<Integer>();
HashSet<Integer> alreadyFound = new HashSet<Integer>();
candidates.addAll(new IntegerRange(graph.getNodeCount()));
findCliques(result, potentialClique, candidates, alreadyFound);
}
/**
* Returns the list of all maximal cliques
*/
public List<List<Integer>> getMaximalCliques() {
List<List<Integer>> result = new ArrayList<List<Integer>>();
collectMaximalCliques(result);
return result;
}
/**
* Checks whether at least one node of the given node array is connected
* to all the candidates.
*
* @param nodes the nodes being checked
* @param candidates the list of candidates
* @return true if at least one node is connected to all the candidates,
* false otherwise
*/
protected boolean isAnyConnectedToAllCandidates(HashSet<Integer> nodes,
HashSet<Integer> candidates) {
Graph graph = this.getGraph();
HashSet<Integer> neiSet = new HashSet<Integer>();
for (int node: nodes) {
int[] neis = graph.getAdjacentNodeIndicesArray(node, Directedness.ALL);
if (neis.length < candidates.size())
continue;
neiSet.clear();
for (int nei: neis)
neiSet.add(nei);
if (neiSet.size() < candidates.size())
continue;
neiSet.retainAll(candidates);
if (neiSet.size() == candidates.size())
return true;
}
return false;
}
}