Intrusive splay tree based associative containers: splay_set, splay_multiset and , splay_tree

| Home | Libraries | People | FAQ | More

## Intrusive splay tree based associative containers: splay_set, splay_multiset and , splay_tree

C++ associative containers are usually based on red-black tree implementations (e.g.: STL, Boost.Intrusive associative containers). However, there are other interesting data structures that offer some advantages (and also disadvantages).

Splay trees are self-adjusting binary search trees used typically in caches, memory allocators and other applications, because splay trees have a "caching effect": recently accessed elements have better access times than elements accessed less frequently. For more information on splay trees see the corresponding Wikipedia entry.

Boost.Intrusive offers 3 containers based on splay trees: `splay_set`, `splay_multiset` and `splaytree`. The first two are similar to `set` or `multiset` and the latter is a generalization that offers functions both to insert unique and multiple keys.

The memory overhead of these containers with Boost.Intrusive hooks is usually 3 pointers. An empty, non constant-time size splay container has also a size of 3 pointers.

### Advantages and disadvantages of splay tree based containers

Splay tree based intrusive containers have logarithmic complexity in many operations like searches, insertions, erasures, etc., but if some elements are more frequently accessed than others, splay trees perform faster searches than equivalent balanced binary trees (such as red-black trees).

The caching effect offered by splay trees comes with a cost: the tree must be rebalanced when an element is searched. To maintain const-correctness and thread-safety guarantees, this caching effect is not updated when const versions of search functions like `find()`, `lower_bound()`, `upper_bound()`, `equal_range()`, `count()`... are called. This means that using splay-tree based associative containers as drop-in replacements of `set`/ `multiset`, specially for const search functions, might not result in desired performance improvements.

If element searches are randomized, the tree will be continuously srebalanced without taking advantage of the cache effect, so splay trees can offer worse performance than other balanced trees for several search patterns.

Boost.Intrusive splay associative containers don't use their own hook types but plain Binary search tree hooks. See Binary search tree hooks: bs_set_base_hook and bs_set_member_hook section for more information about these hooks.

### splay_set, splay_multiset and splaytree containers

    template <class T, class ...Options>
    class splay_set;

    template <class T, class ...Options>
    class splay_multiset;

    template <class T, class ...Options>
    class splaytree;

These containers receive the same options explained in the section How to use Boost.Intrusive:

- `base_hook<class Hook>` / `member_hook<class T, class Hook, Hook T::* PtrToMember>` / `value_traits<class ValueTraits>`: To specify the hook type or value traits used to configure the container. (To learn about value traits go to the section Containers with custom ValueTraits.)
- `constant_time_size<bool Enabled>`: To activate the constant-time `size()` operation. Default: `constant_time_size<true>`
- `size_type<bool Enabled>`: To specify the type that will be used to store the size of the container. Default: `size_type<std::size_t>`

And they also can receive an additional option:

- `compare<class Compare>`: Comparison function for the objects to be inserted in containers. The comparison functor must induce a strict weak ordering. Default: `compare< std::less<key_type> >`
- `key_of_value<class KeyOfValueFunctionObject>`: A function object that will define the `key_type` of the value type to be stored. This type will allow a map-like interface. See Map and multimap-like interface with set and multiset for details. Default: `key_type` is equal to `value_type` (set-like interface).

### Example

Now let's see a small example using `splay_set`/ `splay_multiset` containers:

    #include <boost/intrusive/splay_set.hpp>
    #include <vector>
    #include <functional>

    using namespace boost::intrusive;

    class mytag;

    class MyClass
       : public bs_set_base_hook<>
    {
       int int_;

       public:
       //This is a member hook
       bs_set_member_hook<> member_hook_;

       MyClass(int i)
          :  int_(i)
          {}
       friend bool operator< (const MyClass &a, const MyClass &b)
          {  return a.int_ < b.int_;  }
       friend bool operator> (const MyClass &a, const MyClass &b)
          {  return a.int_ > b.int_;  }
       friend bool operator== (const MyClass &a, const MyClass &b)
          {  return a.int_ == b.int_;  }
    };

    //Define a set using the base hook that will store values in reverse order
    typedef splay_set< MyClass, compare<std::greater<MyClass> > >     BaseSplaySet;

    //Define an multiset using the member hook
    typedef member_hook<MyClass, bs_set_member_hook<>, &MyClass::member_hook_> MemberOption;
    typedef splay_multiset< MyClass, MemberOption>   MemberSplayMultiset;

    int main()
    {
       typedef std::vector<MyClass>::iterator VectIt;

       //Create several MyClass objects, each one with a different value
       std::vector<MyClass> values;
       for(int i = 0; i < 100; ++i)  values.push_back(MyClass(i));

       BaseSplaySet   baseset;
       MemberSplayMultiset membermultiset;

       //Insert values in the container
       for(VectIt it(values.begin()), itend(values.end()); it != itend; ++it){
          baseset.insert(*it);
          membermultiset.insert(*it);
       }

       //Now test sets
       {
          BaseSplaySet::reverse_iterator rbit(baseset.rbegin());
          MemberSplayMultiset::iterator mit(membermultiset.begin());
          VectIt it(values.begin()), itend(values.end());

          //Test the objects inserted in the base hook set
          for(; it != itend; ++it, ++rbit){
             if(&*rbit != &*it)   return 1;
          }

          //Test the objects inserted in member and binary search hook sets
          for(it = values.begin(); it != itend; ++it, ++mit){
             if(&*mit != &*it)    return 1;
          }
       }
       return 0;
    }