In-order bidirectional iterator classΒΆ

If the tree is not empty, the constructor sets current to the minimun node, the left-most node with the smallest key. The Node *successor() method called by iterator_inorder& operator++() is described below. An enum class implements a finite state machine of three possible iterator positions, with at_beg and at_end denoting one-before the first tree value and one-after the last tree key, repectively, and the state between modeling the state between these two settings.

class iterator_inorder {

   bstree<Key, Value> *ptree;
   using node_type = bstree<Key, Value>::node_type;
   node_type *current;

   Node *successor();
   enum class position {at_beg, between, at_end};
   position pos;
   // snip...private methods are show later on.
  public:

    using difference_type  = std::ptrdiff_t;
    using value_type       = bstree<Key, Value>::value_type;
    using reference        = value_type&;
    using pointer          = value_type*;

    using iterator_category = std::bidirectional_iterator_tag;

    iterator_inorder() : current{nullptr}, ptree{nullptr}, pos{position::at_end} { }

    explicit iterator_inorder(bstree<Key, Value>& tree) : ptree{&tree}
    {
       if (!ptree->root) {
          pos = position::at_end;
          current = nullptr;
       } else {
         pos = position::at_beg;
         current = min(ptree->root.get());
       }
    }

    // Ctor for return the iterator_inorder returned by end();
    iterator_inorder(bstree<Key, Value>& tree, int dummy) : ptree{&tree}, pos{position::at_end}
    {
       current = (!ptree->root) ?  nullptr : max(ptree->root.get());
    }

    iterator_inorder(const iterator_inorder& lhs) : current{lhs.current}, ptree{lhs.ptree}, pos{lhs.pos}
    {
    }

    iterator_inorder& operator=(const iterator_inorder& lhs)
    {
        if (this != &lhs) {
            current = lhs.current;
            ptree = lhs.ptree;
            pos = lhs.pos;
        }
        return *this;
    }

    iterator_inorder& operator++() noexcept
    {
      switch (pos) {
         case position::at_end:
             break;
         case position::at_beg:
         case position::between:
         {
             auto next = successor();

             if (current == next) pos = position::at_end;
             else
               current = next;
         }
         break;
         default:
         break;
       }
       return *this;
    }

    iterator_inorder operator++(int) noexcept
    {
       iterator_inorder tmp(*this);
       operator++();
       return tmp;
    }

    iterator_inorder& operator--() noexcept
    {
       switch(pos) {
           case position::at_beg: break;
           case position::at_end:
               pos = position::between;
               break;
           case position::between:
           {
             auto prev = predecessor();

            if (prev == current) pos = position::at_beg;
            else
                current = prev;
           }
           break;
           default: break;
       }
       return *this;
    }

    iterator_inorder operator--(int) noexcept
    {
       iterator_inorder tmp(*this);
       operator--();
       return tmp;
    }

    reference operator*() const noexcept
    {
        return current->__get_value();
    }

    pointer operator->() const noexcept
    {
       return &(operator*());
    }

    friend bool
    operator==(const iterator_inorder& __x, const iterator_inorder& __y) noexcept
    {
      if (__x.ptree == __y.ptree) {

         // If we are not in_between...check whether both iterators are at the end...
         if (__x.pos == position::at_end && __y.pos == position::at_end) return true;

         // ...or at beginning.
         else if (__x.pos == position::at_beg && __y.pos == position::at_beg) return true;

         else if (__x.pos == __y.pos && __x.current == __y.current) return true;// else check whether pos and current are all equal.
      }
      return false;
    }

    friend bool
    operator!=(const iterator_inorder& __x, const iterator_inorder& __y) noexcept
    {
       return !operator==(__x, __y);
    }
   };

These bstree uses these methods to return iterator_inorder objects:

   iterator_inorder begin() noexcept
   {
       iterator_inorder iter{*this};
       return iter;
   }

   // Retruns an iterator representing one-past the one.
   iterator_inorder end() noexcept
   {
       iterator_inorder iter{*this, 1};
       return iter;
   }

   using reverse_iterator = std::reverse_iterator<iterator_inorder>;

   reverse_iterator rbegin() noexcept
   {
      return std::make_reverse_iterator(this->end());
   }

   reverse_iterator rend() noexcept
   {
      return std::make_reverse_iterator(this->begin());
   }
};

Before successor() advances to the in-order successor, it checks if we are already at position::at_end. If not, and if current has a right child, the right child is the successor, and we are done. If there is no right child, we ascend the parent ancestor chain until we encounter a parent that is not a right child (of its parent). This will be the first value in the tree greater than current->key(), and thus the in-order successor. If we reach the root before finding such a parent, there is no in-order successor. This situation only occurs when current points to the largest, the right-most node in the tree. In this case, we simply return current.

Node *successor()
{
    if (current == nullptr || pos == position::at_end) return current;

    Node *__y = current;

    if (__y->right) { // current has a right child, a greater value to the right

        __y = __y->right.get();

        while (__y->left) // Get the smallest value in its right subptree, the smallest value in the r. subptree.
           __y = __y->left.get();

    } else {

        auto parent = __y->parent;

        // Ascend to the first parent ancestor that is not a right child, and thus is greater than __y
        while (__y == parent->right.get()) {

            if (parent == ptree->root.get())  // We reached the root. so there is no successor
                return current;

            __y = parent;
            parent = parent->parent;
        }
        __y = parent; // Set __y to first parent ancestor that is not a right child.
    }
    return __y;
}

predecessor() is similar to successor(), but it first checks if we are already at position::at_beg. If not, and if current has a leftt child, the left child is the successor, and we are done. If there is no left child, we ascend the parent ancestor chain until we encounter a parent that is not a left child (of its parent). This will be the first value in the tree less than current->key(), and thus the in-order predecessor. If we reach the root before finding such a parent, there is no in-order predecessor. This situation only occurs when current points to the smallest, the left-most node in the tree. In this case, we simply return current.

Node *predecessor()
{
   if (current == nullptr || pos == position::at_beg) return current;

   Node *__x = current;

   if (__x->left) { // Unlike successor() we check left child before right child.

        auto __y = __x->left.get();

        while (__y->right) // Get its largest value. This is the predecessor to current.
          __y = __y->right.get();

        __x = __y;

    } else { // When we ascend, we look for a parent ancestor that is not a left child, unlike increment that looks for 'not a right child'.

        auto parent = __x->parent;

        // Ascend to first parent ancestor that is not a left child and thus is less than __x.
        while (__x == parent->left.get()) {

           // If the parent is the root -> there is no predecessor.
           if (parent == ptree->root.get()) return current;

            __x = parent;
            parent = parent->parent;
        }

        __x = parent; // Set __x to first parent less than __x.
    }
    return __x;
}