You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and dots ('.'), can be up to 35 characters long. Letters must be lowercase.
309 lines
12 KiB
309 lines
12 KiB
// (C) Copyright 2007-2009 Andrew Sutton |
|
// |
|
// Use, modification and distribution are subject to the |
|
// Boost Software License, Version 1.0 (See accompanying file |
|
// LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt) |
|
|
|
#ifndef BOOST_GRAPH_CLIQUE_HPP |
|
#define BOOST_GRAPH_CLIQUE_HPP |
|
|
|
#include <vector> |
|
#include <deque> |
|
#include <boost/config.hpp> |
|
|
|
#include <boost/graph/graph_concepts.hpp> |
|
#include <boost/graph/lookup_edge.hpp> |
|
|
|
#include <boost/concept/detail/concept_def.hpp> |
|
namespace boost { |
|
namespace concepts { |
|
BOOST_concept(CliqueVisitor,(Visitor)(Clique)(Graph)) |
|
{ |
|
BOOST_CONCEPT_USAGE(CliqueVisitor) |
|
{ |
|
vis.clique(k, g); |
|
} |
|
private: |
|
Visitor vis; |
|
Graph g; |
|
Clique k; |
|
}; |
|
} /* namespace concepts */ |
|
using concepts::CliqueVisitorConcept; |
|
} /* namespace boost */ |
|
#include <boost/concept/detail/concept_undef.hpp> |
|
|
|
namespace boost |
|
{ |
|
// The algorithm implemented in this paper is based on the so-called |
|
// Algorithm 457, published as: |
|
// |
|
// @article{362367, |
|
// author = {Coen Bron and Joep Kerbosch}, |
|
// title = {Algorithm 457: finding all cliques of an undirected graph}, |
|
// journal = {Communications of the ACM}, |
|
// volume = {16}, |
|
// number = {9}, |
|
// year = {1973}, |
|
// issn = {0001-0782}, |
|
// pages = {575--577}, |
|
// doi = {http://doi.acm.org/10.1145/362342.362367}, |
|
// publisher = {ACM Press}, |
|
// address = {New York, NY, USA}, |
|
// } |
|
// |
|
// Sort of. This implementation is adapted from the 1st version of the |
|
// algorithm and does not implement the candidate selection optimization |
|
// described as published - it could, it just doesn't yet. |
|
// |
|
// The algorithm is given as proportional to (3.14)^(n/3) power. This is |
|
// not the same as O(...), but based on time measures and approximation. |
|
// |
|
// Unfortunately, this implementation may be less efficient on non- |
|
// AdjacencyMatrix modeled graphs due to the non-constant implementation |
|
// of the edge(u,v,g) functions. |
|
// |
|
// TODO: It might be worthwhile to provide functionality for passing |
|
// a connectivity matrix to improve the efficiency of those lookups |
|
// when needed. This could simply be passed as a BooleanMatrix |
|
// s.t. edge(u,v,B) returns true or false. This could easily be |
|
// abstracted for adjacency matricies. |
|
// |
|
// The following paper is interesting for a number of reasons. First, |
|
// it lists a number of other such algorithms and second, it describes |
|
// a new algorithm (that does not appear to require the edge(u,v,g) |
|
// function and appears fairly efficient. It is probably worth investigating. |
|
// |
|
// @article{DBLP:journals/tcs/TomitaTT06, |
|
// author = {Etsuji Tomita and Akira Tanaka and Haruhisa Takahashi}, |
|
// title = {The worst-case time complexity for generating all maximal cliques and computational experiments}, |
|
// journal = {Theor. Comput. Sci.}, |
|
// volume = {363}, |
|
// number = {1}, |
|
// year = {2006}, |
|
// pages = {28-42} |
|
// ee = {http://dx.doi.org/10.1016/j.tcs.2006.06.015} |
|
// } |
|
|
|
/** |
|
* The default clique_visitor supplies an empty visitation function. |
|
*/ |
|
struct clique_visitor |
|
{ |
|
template <typename VertexSet, typename Graph> |
|
void clique(const VertexSet&, Graph&) |
|
{ } |
|
}; |
|
|
|
/** |
|
* The max_clique_visitor records the size of the maximum clique (but not the |
|
* clique itself). |
|
*/ |
|
struct max_clique_visitor |
|
{ |
|
max_clique_visitor(std::size_t& max) |
|
: maximum(max) |
|
{ } |
|
|
|
template <typename Clique, typename Graph> |
|
inline void clique(const Clique& p, const Graph& g) |
|
{ |
|
BOOST_USING_STD_MAX(); |
|
maximum = max BOOST_PREVENT_MACRO_SUBSTITUTION (maximum, p.size()); |
|
} |
|
std::size_t& maximum; |
|
}; |
|
|
|
inline max_clique_visitor find_max_clique(std::size_t& max) |
|
{ return max_clique_visitor(max); } |
|
|
|
namespace detail |
|
{ |
|
template <typename Graph> |
|
inline bool |
|
is_connected_to_clique(const Graph& g, |
|
typename graph_traits<Graph>::vertex_descriptor u, |
|
typename graph_traits<Graph>::vertex_descriptor v, |
|
typename graph_traits<Graph>::undirected_category) |
|
{ |
|
return lookup_edge(u, v, g).second; |
|
} |
|
|
|
template <typename Graph> |
|
inline bool |
|
is_connected_to_clique(const Graph& g, |
|
typename graph_traits<Graph>::vertex_descriptor u, |
|
typename graph_traits<Graph>::vertex_descriptor v, |
|
typename graph_traits<Graph>::directed_category) |
|
{ |
|
// Note that this could alternate between using an || to determine |
|
// full connectivity. I believe that this should produce strongly |
|
// connected components. Note that using && instead of || will |
|
// change the results to a fully connected subgraph (i.e., symmetric |
|
// edges between all vertices s.t., if a->b, then b->a. |
|
return lookup_edge(u, v, g).second && lookup_edge(v, u, g).second; |
|
} |
|
|
|
template <typename Graph, typename Container> |
|
inline void |
|
filter_unconnected_vertices(const Graph& g, |
|
typename graph_traits<Graph>::vertex_descriptor v, |
|
const Container& in, |
|
Container& out) |
|
{ |
|
function_requires< GraphConcept<Graph> >(); |
|
|
|
typename graph_traits<Graph>::directed_category cat; |
|
typename Container::const_iterator i, end = in.end(); |
|
for(i = in.begin(); i != end; ++i) { |
|
if(is_connected_to_clique(g, v, *i, cat)) { |
|
out.push_back(*i); |
|
} |
|
} |
|
} |
|
|
|
template < |
|
typename Graph, |
|
typename Clique, // compsub type |
|
typename Container, // candidates/not type |
|
typename Visitor> |
|
void extend_clique(const Graph& g, |
|
Clique& clique, |
|
Container& cands, |
|
Container& nots, |
|
Visitor vis, |
|
std::size_t min) |
|
{ |
|
function_requires< GraphConcept<Graph> >(); |
|
function_requires< CliqueVisitorConcept<Visitor,Clique,Graph> >(); |
|
typedef typename graph_traits<Graph>::vertex_descriptor Vertex; |
|
|
|
// Is there vertex in nots that is connected to all vertices |
|
// in the candidate set? If so, no clique can ever be found. |
|
// This could be broken out into a separate function. |
|
{ |
|
typename Container::iterator ni, nend = nots.end(); |
|
typename Container::iterator ci, cend = cands.end(); |
|
for(ni = nots.begin(); ni != nend; ++ni) { |
|
for(ci = cands.begin(); ci != cend; ++ci) { |
|
// if we don't find an edge, then we're okay. |
|
if(!lookup_edge(*ni, *ci, g).second) break; |
|
} |
|
// if we iterated all the way to the end, then *ni |
|
// is connected to all *ci |
|
if(ci == cend) break; |
|
} |
|
// if we broke early, we found *ni connected to all *ci |
|
if(ni != nend) return; |
|
} |
|
|
|
// TODO: the original algorithm 457 describes an alternative |
|
// (albeit really complicated) mechanism for selecting candidates. |
|
// The given optimizaiton seeks to bring about the above |
|
// condition sooner (i.e., there is a vertex in the not set |
|
// that is connected to all candidates). unfortunately, the |
|
// method they give for doing this is fairly unclear. |
|
|
|
// basically, for every vertex in not, we should know how many |
|
// vertices it is disconnected from in the candidate set. if |
|
// we fix some vertex in the not set, then we want to keep |
|
// choosing vertices that are not connected to that fixed vertex. |
|
// apparently, by selecting fix point with the minimum number |
|
// of disconnections (i.e., the maximum number of connections |
|
// within the candidate set), then the previous condition wil |
|
// be reached sooner. |
|
|
|
// there's some other stuff about using the number of disconnects |
|
// as a counter, but i'm jot really sure i followed it. |
|
|
|
// TODO: If we min-sized cliques to visit, then theoretically, we |
|
// should be able to stop recursing if the clique falls below that |
|
// size - maybe? |
|
|
|
// otherwise, iterate over candidates and and test |
|
// for maxmimal cliquiness. |
|
typename Container::iterator i, j, end = cands.end(); |
|
for(i = cands.begin(); i != cands.end(); ) { |
|
Vertex candidate = *i; |
|
|
|
// add the candidate to the clique (keeping the iterator!) |
|
// typename Clique::iterator ci = clique.insert(clique.end(), candidate); |
|
clique.push_back(candidate); |
|
|
|
// remove it from the candidate set |
|
i = cands.erase(i); |
|
|
|
// build new candidate and not sets by removing all vertices |
|
// that are not connected to the current candidate vertex. |
|
// these actually invert the operation, adding them to the new |
|
// sets if the vertices are connected. its semantically the same. |
|
Container new_cands, new_nots; |
|
filter_unconnected_vertices(g, candidate, cands, new_cands); |
|
filter_unconnected_vertices(g, candidate, nots, new_nots); |
|
|
|
if(new_cands.empty() && new_nots.empty()) { |
|
// our current clique is maximal since there's nothing |
|
// that's connected that we haven't already visited. If |
|
// the clique is below our radar, then we won't visit it. |
|
if(clique.size() >= min) { |
|
vis.clique(clique, g); |
|
} |
|
} |
|
else { |
|
// recurse to explore the new candidates |
|
extend_clique(g, clique, new_cands, new_nots, vis, min); |
|
} |
|
|
|
// we're done with this vertex, so we need to move it |
|
// to the nots, and remove the candidate from the clique. |
|
nots.push_back(candidate); |
|
clique.pop_back(); |
|
} |
|
} |
|
} /* namespace detail */ |
|
|
|
template <typename Graph, typename Visitor> |
|
inline void |
|
bron_kerbosch_all_cliques(const Graph& g, Visitor vis, std::size_t min) |
|
{ |
|
function_requires< IncidenceGraphConcept<Graph> >(); |
|
function_requires< VertexListGraphConcept<Graph> >(); |
|
function_requires< VertexIndexGraphConcept<Graph> >(); |
|
function_requires< AdjacencyMatrixConcept<Graph> >(); // Structural requirement only |
|
typedef typename graph_traits<Graph>::vertex_descriptor Vertex; |
|
typedef typename graph_traits<Graph>::vertex_iterator VertexIterator; |
|
typedef std::vector<Vertex> VertexSet; |
|
typedef std::deque<Vertex> Clique; |
|
function_requires< CliqueVisitorConcept<Visitor,Clique,Graph> >(); |
|
|
|
// NOTE: We're using a deque to implement the clique, because it provides |
|
// constant inserts and removals at the end and also a constant size. |
|
|
|
VertexIterator i, end; |
|
boost::tie(i, end) = vertices(g); |
|
VertexSet cands(i, end); // start with all vertices as candidates |
|
VertexSet nots; // start with no vertices visited |
|
|
|
Clique clique; // the first clique is an empty vertex set |
|
detail::extend_clique(g, clique, cands, nots, vis, min); |
|
} |
|
|
|
// NOTE: By default the minimum number of vertices per clique is set at 2 |
|
// because singleton cliques aren't really very interesting. |
|
template <typename Graph, typename Visitor> |
|
inline void |
|
bron_kerbosch_all_cliques(const Graph& g, Visitor vis) |
|
{ bron_kerbosch_all_cliques(g, vis, 2); } |
|
|
|
template <typename Graph> |
|
inline std::size_t |
|
bron_kerbosch_clique_number(const Graph& g) |
|
{ |
|
std::size_t ret = 0; |
|
bron_kerbosch_all_cliques(g, find_max_clique(ret)); |
|
return ret; |
|
} |
|
|
|
} /* namespace boost */ |
|
|
|
#endif
|
|
|