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 @@
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
+
+
+
+
+
+ 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
+
+
+
+ ElementParentMap |
+
+ Must be a model of ReadWritePropertyMap
+ and the key and value type the same as the trees' element type. |
+
+
+
+ ElementChildMap |
+
+ Default 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
+
+
+
+ Member |
+
+ Description |
+
+
+
+ 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
+
+
+
+ Parameter |
+
+ Description |
+
+ Default |
+
+
+
+ ID |
+
+ must 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 |
+
+
+
+ InverseID |
+
+ must 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())
+
+
+
+
+
+ Revised
+
+ June, 2020
+
+
+
+ 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