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