diff --git a/doc/bibliography.html b/doc/bibliography.html index b541abf62..11a4597f8 100644 --- a/doc/bibliography.html +++ b/doc/bibliography.html @@ -453,6 +453,11 @@

Bibliography

Data Structures for Weighted Matching and Nearest Common Ancestors with Linking
Proceedings of the First Annual ACM-SIAM Symposium on Discrete Algorithms, pp. 434-443, 1990. +

77 +
Robert Endre Tarjan
+Linking and Cutting Trees
+Data Structures and Network Algorithms, ISBN: 978-0-89871-187-5, pp. 59-70, 1983. +
diff --git a/doc/link_cut_trees.html b/doc/link_cut_trees.html new file mode 100644 index 000000000..0c9564301 --- /dev/null +++ b/doc/link_cut_trees.html @@ -0,0 +1,209 @@ + + + + + + + + Boost Link/Cut Trees + + + + C++ Boost
+ +

Link/Cut Trees

+
link_cut_trees<ElementParentMap, ElementChildMap>
+ +

A link/cut-trees data structure + maintains a forest of element nodes subject to dynamic linking and cutting operations. + Rooted trees are encoded in both the ElementParentMap and ElementChildMap + property maps. Splay trees are used internally to represent every link/cut-tree [77]..

+ +

Where Defined

boost/graph/link_cut_trees.hpp + +

Template Parameters

+ + + + + + + + + + + + + +
ElementParentMapMust be a model of ReadWritePropertyMap + and the key and value type the same as the trees' element type.
ElementChildMapDefault the same as ElementParentMap. Also must be a model of ReadWritePropertyMap + and the key and value type the same as the trees' element type.
+ +

Example

+ +

A typical usage pattern for link_cut_trees can be seen in the + dynamic connectivity problem for acyclic graphs. Given two nodes x and y, + they are connected if and only if find_root(x) == find_root(y).

+ +
+  ...
+  link_cut_trees<ElementParentMap, ElementChildMap> lct(parent_map, left_child_map, right_child_map);
+
+  for (ui = vertices(G).first; ui != vertices(G).second; ++ui)
+    lct.make_tree(*ui);
+  ...
+  while ( !Q.empty() ) {
+    e = Q.front();
+    Q.pop();
+    u = lct.find_root(source(e));
+    v = lct.find_root(target(e));
+    if ( u != v ) {
+      *out++ = e;
+      lct.link(u, v);
+    }
+  }
+  ...
+  for (ui = vertices(G).first; ui != vertices(G).second; ++ui)
+    lct.cut(*ui);
+
+ +

Members

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MemberDescription
link_cut_trees(ElementParentMap p, ElementChildMap l, ElementChildMap r)Constructor.
link_cut_trees(const link_cut_trees& c)Copy constructor.
template <class Element>
+ void make_tree(Element x)
Create a singleton tree containing element x.
template <class Element>
+ Element find_root(Element x)
Return the root of the tree containing element x.
template <class Element>
+ void link(Element x, Element y)
Make the tree rooted at element x a subtree of Element y. Element x must be a tree root.
template <class Element>
+ void cut(Element x)
Remove the edge connecting x to its parent and make x a tree root.
template <class Element>
+ Element lowest_common_ancestor(Element x, Element y)
Return the lowest (i.e. nearest) common ancestor of elements x and y. Elements x and y must have the same root.
+ +

Complexity

+ +

The amortized time complexity is O(log n) for link, cut, find_root and lowest_common_ancestor operations, + where n is the number of tree nodes. Operation make_tree is of constant time complexity.

+ +
+
link_cut_trees_with_storage<ID, InverseID>
+ +

This class manages the storage for the parent and children properties + internally. The storage is in boost::unordered_map, which is indexed by element ID, + hence the requirement for the ID and InverseID functors. + +

Template Parameters

+ + + + + + + + + + + + + + + + + + + + + + + + + + +
ParameterDescriptionDefault
IDmust be a model of ReadablePropertyMap that + maps elements to values of any type that can be hashed to std::size_t.boost::identity_property_map
InverseIDmust be a model of ReadablePropertyMap that + maps values of property_traits<ID>::value_type to elements.boost::unordered_map
+ +

Members

+ +

This class has all of the members in link_cut_trees as well as + the following constructor.

+
link_cut_trees_with_storage(ID id = ID(), InverseID inverse_id = InverseID())
+ +
+ +

Valid HTML 4.01 Transitional

+ +

Revised + + June, 2020

+ + + + + + + +
Copyright © 2019 + Yi Ji, Peking University (jiy@pku.edu.cn)
+
+ +

Distributed under the Boost Software License, Version 1.0. (See + accompanying file LICENSE_1_0.txt or + copy at http://www.boost.org/LICENSE_1_0.txt)

+ + \ No newline at end of file diff --git a/include/boost/graph/link_cut_trees.hpp b/include/boost/graph/link_cut_trees.hpp new file mode 100644 index 000000000..d39444c08 --- /dev/null +++ b/include/boost/graph/link_cut_trees.hpp @@ -0,0 +1,277 @@ +// +//======================================================================= +// Copyright 2019 +// Author: Yi Ji +// +// Distributed under the Boost Software License, Version 1.0. (See +// accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +//======================================================================= +// +#ifndef BOOST_LINK_CUT_TREES_HPP +#define BOOST_LINK_CUT_TREES_HPP + +#include +#include + +namespace boost +{ + template + class link_cut_trees + { + public: + link_cut_trees(ElementParentMap p, ElementChildMap l, ElementChildMap r) : parent(p), left(l), right(r) {} + + link_cut_trees(const link_cut_trees &c) + : parent(c.parent), left(c.left), right(c.right) {} + + template + void make_tree(Element x) + { + put(parent, x, x); + put(right, x, x); + put(left, x, x); + } + + template + Element find_root(Element x) + { + return find_tail(expose(x)); + } + + template + void link(Element x, Element y) + { + BOOST_ASSERT(find_root(x) == x); // Element x must be a tree root + Element r = expose(x); + r = join(r, r, expose(y)); + put_successor(r, r); + } + + template + void cut(Element x) + { + expose(x); + std::pair uv = split(x); + put_successor(x, x); + put_successor(uv.second, uv.second); + } + + template + Element lowest_common_ancestor(Element x, Element y) + { + BOOST_ASSERT(find_root(x) == find_root(y)); // Elements x and y must have same root + expose(x); + return expose(y); + } + + private: + ElementParentMap parent; + ElementChildMap left, right; + + template + Element get_parent(Element x) const + { + Element x_parent = get(parent, x); + if (get(left, x_parent) == x || get(right, x_parent) == x) + return x_parent; + return x; // x_parent is actually x_successor when x has no parent + } + + template + Element get_successor(Element x) const + { + return get(parent, x); + } + + template + void put_successor(Element x, Element x_successor) + { + put(parent, x, x_successor); + } + + template + void rotate(const Element x, const ElementChildMap &side) + { + const ElementChildMap &opposite = (&side == &left) ? right : left; + const Element pivot = get(side, x); + const Element x_parent = get_parent(x); + const Element pivot_side = get(opposite, pivot); + + if (x_parent != x) + { + put(parent, pivot, x_parent); + put(get_side(x), x_parent, pivot); + } + else + { + Element x_successor = get_successor(x); + put_successor(pivot, x == x_successor ? pivot : x_successor); + } + + if (pivot_side != pivot) + { + put(side, x, pivot_side); + put(parent, pivot_side, x); + } + else + put(side, x, x); + + put(opposite, pivot, x); + put(parent, x, pivot); + } + + template + ElementChildMap& get_side(Element x) + { + Element x_parent = get_parent(x); + if (get(left, x_parent) == x) + return left; + return right; + } + + template + void splay(Element x) + { + for (Element x_parent = get_parent(x); x != x_parent; x_parent = get_parent(x)) + { + const Element x_grandparent = get_parent(x_parent); + const ElementChildMap &x_side = get_side(x); + const ElementChildMap &x_parent_side = get_side(x_parent); + + if (x_grandparent == x_parent) + rotate(x_parent, x_side); + else if (&x_side == &x_parent_side) + { + rotate(x_grandparent, x_parent_side); + rotate(x_parent, x_side); + } + else + { + rotate(x_parent, x_side); + rotate(x_grandparent, x_parent_side); + } + } + } + + template + Element expose(Element x) + { + Element r = x; + while (true) + { + const Element x_successor = get_successor(find_path(x)); + const std::pair uv = split(x); + if (x != uv.first) + put_successor(uv.first, x); + r = join(r, x, uv.second); + if (x == x_successor) + break; + x = x_successor; + } + put_successor(r, r); + return r; + } + + template + Element find_path(Element x) + { + splay(x); + return x; + } + + template + Element find_tail(Element x) + { + while (get(right, x) != x) + x = get(right, x); + splay(x); + return x; + } + + template + Element join(Element u, Element v, Element w) + { + if (u != v) + put(parent, u, v); + if (w != v) + put(parent, w, v); + put(left, v, u); + put(right, v, w); + return v; + } + + template + std::pair split(Element x) + { + splay(x); + Element x_left = get(left, x); + Element x_right = get(right, x); + if (x_left != x) + put(parent, x_left, x_left); + if (x_right != x) + put(parent, x_right, x_right); + put(left, x, x); + put(right, x, x); + return std::make_pair<>(x_left, x_right); + } + }; + + + template ::value_type, typename property_traits::key_type>, + class IndexMapContainer = boost::unordered_map::value_type, typename property_traits::value_type> > + class link_cut_trees_with_storage : + public link_cut_trees > + { + public: + typedef typename property_traits::key_type Vertex; + typedef typename property_traits::value_type Index; + typedef associative_property_map IndexMap; + typedef link_cut_trees LCT; + + link_cut_trees_with_storage(ID id_ = ID(), InverseID inverse_id = InverseID()) : + LCT(IndexMap(parent_map), IndexMap(left_map), IndexMap(right_map)), + id(id_), + id_to_vertex(inverse_id) {} + + template + void make_tree(Vertex x) + { + const Index x_id = get(id, x); + LCT::make_tree(x_id); + id_to_vertex[x_id] = x; + } + + template + Vertex find_root(Vertex x) + { + return id_to_vertex[LCT::find_root(get(id, x))]; + } + + template + void link(Vertex x, Vertex y) + { + LCT::link(get(id, x), get(id, y)); + } + + template + void cut(Vertex x) + { + LCT::cut(get(id, x)); + } + + template + Vertex lowest_common_ancestor(Vertex x, Vertex y) + { + return id_to_vertex[LCT::lowest_common_ancestor(get(id, x), get(id, y))]; + } + + private: + ID id; + InverseID id_to_vertex; + IndexMapContainer parent_map, left_map, right_map; + }; +} + +#endif // BOOST_LINK_CUT_TREES_HPP \ No newline at end of file diff --git a/test/Jamfile.v2 b/test/Jamfile.v2 index b4fcfd4f3..ae1616777 100644 --- a/test/Jamfile.v2 +++ b/test/Jamfile.v2 @@ -112,6 +112,7 @@ alias graph_test_regular : [ run king_ordering.cpp ] [ run matching_test.cpp ] [ run weighted_matching_test.cpp ] + [ run link_cut_trees_test.cpp ] [ run max_flow_test.cpp ] [ run boykov_kolmogorov_max_flow_test.cpp ] [ run cycle_ratio_tests.cpp ../build//boost_graph ../../regex/build//boost_regex : $(CYCLE_RATIO_INPUT_FILE) ] diff --git a/test/link_cut_trees_test.cpp b/test/link_cut_trees_test.cpp new file mode 100644 index 000000000..b9279a833 --- /dev/null +++ b/test/link_cut_trees_test.cpp @@ -0,0 +1,214 @@ +//======================================================================= +// Copyright (c) 2019 Yi Ji +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// +//======================================================================= + +#define BOOST_TEST_MODULE link_cut_trees_test + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace boost; + +std::size_t lowest_common_ancestor(std::size_t N, std::size_t u, std::size_t w) +{ + const std::size_t size = std::max(u, w) + 1; + std::vector ancester_of_u(size, false), ancester_of_w(size, false); + while (u > 0 || w > 0) + { + ancester_of_u[u] = ancester_of_w[w] = true; + if (ancester_of_u[w]) + { + return w; + } + if (ancester_of_w[u]) + { + return u; + } + u = u > 0 ? (u - 1) / N : 0; + w = w > 0 ? (w - 1) / N : 0; + } + return 0; +} + +template +void test_link_cut_trees(LinkCutTree lct, const std::vector &elements) +{ + BOOST_FOREACH(const Element &ele, elements) + { + lct.make_tree(ele); + BOOST_CHECK(lct.find_root(ele) == ele); + } + + for (std::size_t i = 0; i < elements.size() - 1; ++i) + { + lct.link(elements[i+1], elements[i]); + BOOST_CHECK(lct.find_root(elements[i]) == elements[0]); + BOOST_CHECK(lct.find_root(elements[i+1]) == elements[0]); + BOOST_CHECK(lct.lowest_common_ancestor(elements[i+1], elements[i]) == elements[i]); + } + + BOOST_FOREACH(const Element &ele, elements) + { + lct.cut(ele); + BOOST_CHECK(lct.find_root(ele) == ele); + } + + for (std::size_t N = 2; N < 7; ++N) + { + for (std::size_t i = elements.size() - 1; i > 0; --i) + { + std::size_t i_parent = (i - 1) / N; + lct.link(elements[i], elements[i_parent]); + BOOST_CHECK(lct.lowest_common_ancestor(elements[i], elements[i_parent]) == + elements[i_parent]); + std::deque queue; + queue.push_back(i); + while (!queue.empty()) + { + std::size_t idx = queue.front(); + queue.pop_front(); + BOOST_CHECK(lct.find_root(elements[idx]) == elements[i_parent]); + std::size_t idx_child = (idx + 1) * N; + if (idx_child < elements.size()) + { + for (std::size_t i = 0; i < N; ++i) + { + queue.push_back(idx_child-i); + } + } + } + } + for (std::size_t i = 0; i < elements.size(); ++i) + { + BOOST_CHECK(lct.find_root(elements[i]) == elements[0]); + for (std::size_t j = 0; j < elements.size(); ++j) + { + BOOST_CHECK(lct.lowest_common_ancestor(elements[i], elements[j]) == + elements[lowest_common_ancestor(N, i, j)]); + } + } + BOOST_FOREACH(const Element &ele, adaptors::reverse(elements)) + { + lct.cut(ele); + BOOST_CHECK(lct.find_root(ele) == ele); + } + } +} + +BOOST_AUTO_TEST_CASE(link_cut_trees_test1) +{ + typedef associative_property_map< std::map > map_t; + typedef link_cut_trees link_cut_trees_t; + std::vector elements(100); + boost::range::iota(elements, -49); + std::map parent_map, left_map, right_map; + map_t parent(parent_map), left(left_map), right(right_map); + link_cut_trees_t lct(parent, left, right); + test_link_cut_trees(lct, elements); +} + +BOOST_AUTO_TEST_CASE(link_cut_trees_test2) +{ + typedef associative_property_map< std::map > map_t; + typedef associative_property_map< boost::unordered_map > unordered_map_t; + typedef link_cut_trees link_cut_trees_t; + std::vector elements(20); + boost::range::iota(elements, 'a'); + std::map parent_map; + boost::unordered_map left_map, right_map; + map_t parent(parent_map); + unordered_map_t left(left_map), right(right_map); + link_cut_trees_t lct(parent, left, right); + test_link_cut_trees(lct, elements); +} + +BOOST_AUTO_TEST_CASE(link_cut_trees_test3) +{ + typedef typed_identity_property_map map_t; + typedef link_cut_trees_with_storage link_cut_trees_t; + std::vector elements; + std::vector numbers(100); + boost::range::iota(numbers, -49); + boost::range::transform(numbers, std::back_inserter(elements), boost::bind(lexical_cast, _1)); + link_cut_trees_t lct; + test_link_cut_trees(lct, elements); +} + +BOOST_AUTO_TEST_CASE(link_cut_trees_test4) +{ + typedef associative_property_map< std::map > map_t; + typedef link_cut_trees_with_storage link_cut_trees_t; + std::vector elements; + std::vector numbers(100); + std::map id_map; + map_t id(id_map); + boost::range::iota(numbers, -49); + BOOST_FOREACH(int i, numbers) + { + std::string i_str = lexical_cast(i); + elements.push_back(i_str); + put(id, i_str, i); + } + link_cut_trees_t lct(id); + test_link_cut_trees(lct, elements); +} + +BOOST_AUTO_TEST_CASE(link_cut_trees_test5) +{ + typedef associative_property_map< std::map > id_map_t; + typedef vector_property_map inverse_id_map_t; + typedef link_cut_trees_with_storage link_cut_trees_t; + std::vector elements; + std::vector numbers(100); + std::map id_map; + id_map_t id(id_map); + inverse_id_map_t inverse_id; + boost::range::iota(numbers, -49); + BOOST_FOREACH(int i, numbers) + { + std::string i_str = lexical_cast(i); + elements.push_back(i_str); + put(id, i_str, i+49); + put(inverse_id, i+49, i_str); + } + link_cut_trees_t lct(id, inverse_id); + test_link_cut_trees(lct, elements); +} + +BOOST_AUTO_TEST_CASE(link_cut_trees_test6) +{ + typedef associative_property_map< std::map > id_map_t; + typedef vector_property_map inverse_id_map_t; + typedef std::map index_map_container_t; + typedef link_cut_trees_with_storage link_cut_trees_t; + std::vector elements; + std::vector numbers(100); + std::map id_map; + id_map_t id(id_map); + inverse_id_map_t inverse_id; + boost::range::iota(numbers, -49); + BOOST_FOREACH(int i, numbers) + { + std::string i_str = lexical_cast(i); + elements.push_back(i_str); + put(id, i_str, i+49); + put(inverse_id, i+49, i_str); + } + link_cut_trees_t lct(id, inverse_id); + test_link_cut_trees(lct, elements); +} \ No newline at end of file