diff --git a/include/boost/graph/stoer_wagner_min_cut.hpp b/include/boost/graph/stoer_wagner_min_cut.hpp index 0ae15fbfc..33ef6e969 100644 --- a/include/boost/graph/stoer_wagner_min_cut.hpp +++ b/include/boost/graph/stoer_wagner_min_cut.hpp @@ -29,94 +29,130 @@ namespace boost namespace detail { - template < typename ParityMap, typename WeightMap, typename IndexMap > - class mas_min_cut_visitor : public boost::default_mas_visitor + /** + * \brief Performs a phase of the Stoer-Wagner min-cut algorithm + * + * Performs a phase of the Stoer-Wagner min-cut algorithm. + * + * As described by Stoer & Wagner (1997), a phase is simply a maximum + * adjacency search (also called a maximum cardinality search), which + * results in the selection of two vertices \em s and \em t, and, as a side + * product, a minimum s-t cut of the input graph. Here, + * the input graph is basically \p g, but some vertices are virtually + * assigned to others as a way of viewing \p g as a graph with some sets of + * vertices merged together. + * + * This implementation is a translation of pseudocode by Professor Uri + * Zwick, School of Computer Science, Tel Aviv University. + * + * \pre \p g is a connected, undirected graph + * \param[in] g the input graph + * \param[in] assignments a read/write property map from each vertex to the + * vertex that it is assigned to + * \param[in] assignedVertices a list of vertices that are assigned to + * others + * \param[in] weights a readable property map from each edge to its + * weight (a non-negative value) + * \param[out] pq a keyed, updatable max-priority queue + * \returns a tuple (\em s, \em t, \em w) of the "s" and + * "t" of the minimum s-t cut and the + * cut weight \em w of the minimum s-t cut. + * \see http://www.cs.tau.ac.il/~zwick/grad-algo-08/gmc.pdf + * + * \author Daniel Trebbien + * \date 2010-09-11 + */ + template < class UndirectedGraph, class VertexAssignmentMap, + class WeightMap, class KeyedUpdatablePriorityQueue > + boost::tuple< + typename boost::graph_traits< UndirectedGraph >::vertex_descriptor, + typename boost::graph_traits< UndirectedGraph >::vertex_descriptor, + typename boost::property_traits< WeightMap >::value_type > + stoer_wagner_phase(const UndirectedGraph& g, + VertexAssignmentMap assignments, + const std::set< typename boost::graph_traits< + UndirectedGraph >::vertex_descriptor >& assignedVertices, + WeightMap weights, KeyedUpdatablePriorityQueue& pq) { - typedef one_bit_color_map< IndexMap > InternalParityMap; + typedef + typename boost::graph_traits< UndirectedGraph >::vertex_descriptor + vertex_descriptor; typedef typename boost::property_traits< WeightMap >::value_type weight_type; - public: - template < typename Graph > - mas_min_cut_visitor(const Graph& g, ParityMap parity, - weight_type& cutweight, const WeightMap& weight_map, - IndexMap index_map) - : m_bestParity(parity) - , m_parity(make_one_bit_color_map(num_vertices(g), index_map)) - , m_bestWeight(cutweight) - , m_cutweight(0) - , m_visited(0) - , m_weightMap(weight_map) - { - // set here since the init list sets the reference - m_bestWeight = (std::numeric_limits< weight_type >::max)(); - } - - template < typename Vertex, typename Graph > - void initialize_vertex(Vertex u, const Graph& g) - { - typedef typename boost::property_traits< ParityMap >::value_type - parity_type; - typedef - typename boost::property_traits< InternalParityMap >::value_type - internal_parity_type; - - put(m_parity, u, internal_parity_type(0)); - put(m_bestParity, u, parity_type(0)); - } + BOOST_ASSERT(pq.empty()); + typename KeyedUpdatablePriorityQueue::key_map keys = pq.keys(); - template < typename Edge, typename Graph > - void examine_edge(Edge e, const Graph& g) + BGL_FORALL_VERTICES_T(v, g, UndirectedGraph) { - weight_type w = get(m_weightMap, e); + if (v == get(assignments, v)) + { // foreach u \in V do + put(keys, v, weight_type(0)); - // if the target of e is already marked then decrease cutweight - // otherwise, increase it - if (get(m_parity, boost::target(e, g))) - { - m_cutweight -= w; - } - else - { - m_cutweight += w; + pq.push(v); } } - template < typename Vertex, typename Graph > - void finish_vertex(Vertex u, const Graph& g) - { - typedef - typename boost::property_traits< InternalParityMap >::value_type - internal_parity_type; + BOOST_ASSERT(pq.size() >= 2); - ++m_visited; - put(m_parity, u, internal_parity_type(1)); + vertex_descriptor s + = boost::graph_traits< UndirectedGraph >::null_vertex(); + vertex_descriptor t + = boost::graph_traits< UndirectedGraph >::null_vertex(); + weight_type w; + while (!pq.empty()) + { // while PQ \neq {} do + const vertex_descriptor u = pq.top(); // u = extractmax(PQ) + w = get(keys, u); + pq.pop(); + + s = t; + t = u; + + BGL_FORALL_OUTEDGES_T(u, e, g, UndirectedGraph) + { // foreach (u, v) \in E do + const vertex_descriptor v = get(assignments, target(e, g)); + + if (pq.contains(v)) + { // if v \in PQ then + put(keys, v, + get(keys, v) + + get(weights, + e)); // increasekey(PQ, v, wA(v) + w(u, v)) + pq.update(v); + } + } - if (m_cutweight < m_bestWeight && m_visited < num_vertices(g)) + typename std::set< vertex_descriptor >::const_iterator + assignedVertexIt, + assignedVertexEnd = assignedVertices.end(); + for (assignedVertexIt = assignedVertices.begin(); + assignedVertexIt != assignedVertexEnd; ++assignedVertexIt) { - m_bestWeight = m_cutweight; - BGL_FORALL_VERTICES_T(i, g, Graph) + const vertex_descriptor uPrime = *assignedVertexIt; + + if (get(assignments, uPrime) == u) { - put(m_bestParity, i, get(m_parity, i)); + BGL_FORALL_OUTEDGES_T(uPrime, e, g, UndirectedGraph) + { // foreach (u, v) \in E do + const vertex_descriptor v + = get(assignments, target(e, g)); + + if (pq.contains(v)) + { // if v \in PQ then + put(keys, v, + get(keys, v) + + get(weights, e)); // increasekey(PQ, v, + // wA(v) + w(u, v)) + pq.update(v); + } + } } } } - inline void clear() - { - m_bestWeight = (std::numeric_limits< weight_type >::max)(); - m_visited = 0; - m_cutweight = 0; - } - - private: - ParityMap m_bestParity; - InternalParityMap m_parity; - weight_type& m_bestWeight; - weight_type m_cutweight; - unsigned m_visited; - const WeightMap& m_weightMap; - }; + return boost::make_tuple(s, t, w); + } /** * \brief Computes a min-cut of the input graph @@ -156,44 +192,71 @@ namespace detail vertex_descriptor; typedef typename boost::property_traits< WeightMap >::value_type weight_type; + typedef + typename boost::graph_traits< UndirectedGraph >::vertices_size_type + vertices_size_type; + typedef typename boost::property_traits< ParityMap >::value_type + parity_type; - typename graph_traits< UndirectedGraph >::vertex_iterator u_iter, u_end; + vertices_size_type n = num_vertices(g); - weight_type bestW = (std::numeric_limits< weight_type >::max)(); - weight_type bestThisTime = (std::numeric_limits< weight_type >::max)(); - vertex_descriptor bestStart - = boost::graph_traits< UndirectedGraph >::null_vertex(); + std::set< vertex_descriptor > assignedVertices; + + // initialize `assignments` (all vertices are initially assigned to + // themselves) + BGL_FORALL_VERTICES_T(v, g, UndirectedGraph) { put(assignments, v, v); } - detail::mas_min_cut_visitor< ParityMap, WeightMap, IndexMap > vis( - g, parities, bestThisTime, weights, index_map); + vertex_descriptor s, t; + weight_type bestW; - // for each node in the graph, - for (boost::tie(u_iter, u_end) = vertices(g); u_iter != u_end; ++u_iter) + boost::tie(s, t, bestW) = boost::detail::stoer_wagner_phase( + g, assignments, assignedVertices, weights, pq); + BOOST_ASSERT(s != t); + BGL_FORALL_VERTICES_T(v, g, UndirectedGraph) { - // run the MAS and find the min cut - vis.clear(); - boost::maximum_adjacency_search(g, - boost::weight_map(weights) - .visitor(vis) - .root_vertex(*u_iter) - .vertex_assignment_map(assignments) - .max_priority_queue(pq)); - if (bestThisTime < bestW) + put(parities, v, parity_type(v == t ? 1 : 0)); + } + put(assignments, t, s); + assignedVertices.insert(t); + --n; + + for (; n >= 2; --n) + { + weight_type w; + boost::tie(s, t, w) = boost::detail::stoer_wagner_phase( + g, assignments, assignedVertices, weights, pq); + BOOST_ASSERT(s != t); + + if (w < bestW) + { + BGL_FORALL_VERTICES_T(v, g, UndirectedGraph) + { + put(parities, v, + parity_type(get(assignments, v) == t ? 1 : 0)); + + if (get(assignments, v) + == t) // all vertices that were assigned to t are now + // assigned to s + put(assignments, v, s); + } + + bestW = w; + } + else { - bestW = bestThisTime; - bestStart = *u_iter; + BGL_FORALL_VERTICES_T(v, g, UndirectedGraph) + { + if (get(assignments, v) + == t) // all vertices that were assigned to t are now + // assigned to s + put(assignments, v, s); + } } + put(assignments, t, s); + assignedVertices.insert(t); } - // Run one more time, starting from the best start location, to - // ensure the visitor has the best values. - vis.clear(); - boost::maximum_adjacency_search(g, - boost::vertex_assignment_map(assignments) - .weight_map(weights) - .visitor(vis) - .root_vertex(bestStart) - .max_priority_queue(pq)); + BOOST_ASSERT(pq.empty()); return bestW; } diff --git a/test/stoer_wagner_test.cpp b/test/stoer_wagner_test.cpp index 2b9808616..198121474 100644 --- a/test/stoer_wagner_test.cpp +++ b/test/stoer_wagner_test.cpp @@ -184,6 +184,32 @@ void test4() BOOST_TEST_EQ(parity2, get(parities, 7)); } +// Non regression test for github.com/boostorg/graph/issues/286 +void test5() +{ + edge_t edges[] = { { 0, 1 }, { 0, 2 }, { 0, 3 }, { 1, 2 }, { 1, 3 }, + { 2, 3 }, { 4, 5 }, { 4, 6 }, { 4, 7 }, { 5, 6 }, { 5, 7 }, { 6, 7 }, + { 0, 4 } }; + weight_type ws[] = { 3, 3, 3, 2, 2, 2, 3, 3, 3, 2, 2, 2, 6 }; + undirected_graph g(edges, edges + 13, ws, 8, 13); + + weight_map_type weights = get(boost::edge_weight, g); + std::map< int, bool > parity; + boost::associative_property_map< std::map< int, bool > > parities(parity); + int w + = boost::stoer_wagner_min_cut(g, weights, boost::parity_map(parities)); + BOOST_TEST_EQ(w, 6); + const bool parity0 = get(parities, 0); + BOOST_TEST_EQ(parity0, get(parities, 1)); + BOOST_TEST_EQ(parity0, get(parities, 2)); + BOOST_TEST_EQ(parity0, get(parities, 3)); + const bool parity4 = get(parities, 4); + BOOST_TEST_NE(parity0, parity4); + BOOST_TEST_EQ(parity4, get(parities, 5)); + BOOST_TEST_EQ(parity4, get(parities, 6)); + BOOST_TEST_EQ(parity4, get(parities, 7)); +} + // The input for the `test_prgen` family of tests comes from a program, named // `prgen`, that comes with a package of min-cut solvers by Chandra Chekuri, // Andrew Goldberg, David Karger, Matthew Levine, and Cliff Stein. `prgen` was @@ -285,13 +311,15 @@ void test_prgen_50_70_2() int main(int argc, char* argv[]) { - if (BOOST_TEST(argc == 2)) { + if (BOOST_TEST(argc == 2)) + { test_dir = argv[1]; test0(); test1(); test2(); test3(); test4(); + test5(); test_prgen_20_70_2(); test_prgen_50_70_2(); }