C++, реализация пользовательского итератора для двоичных деревьев (long)

пожалуйста, будьте любезны - это мой первый вопрос. =P

в основном как летний проект я просматривал список структур данных на Википедия страницы и пытается их реализовать. В прошлом семестре я прошел курс на C++ и нашел его очень интересным, в качестве последнего проекта я реализовал Биномиальную кучу - это тоже было очень весело. Может, я и зануда, но мне нравятся структуры данных.

в любом случае, достаточно предыстории. Проект идет хорошо, я начинаю с бинарное дерево. Чтобы пойти намного дальше, мне нужно создать итераторы для пересечения деревьев. Я уже решил, что создам два типа итераторов для каждого метода обхода (обычный итератор и итератор const), я просто не знаю, как это сделать. Я слышал о наследовании от итератора STL или даже использовании boosts iterator_facade (что кажется хорошим вариантом)

Я еще даже не пытался написать код итератора, так как я не знаю, с чего начать, но у меня есть текущий код на GitHub. Вы можете проверить это здесь.

в случае, если вы против github, я вставляю соответствующие определения классов. Реализация этих функций на самом деле не поможет, но если они вам понадобятся по какой-то причине, дайте мне знать. Кроме того, класс node имеет Родительский указатель для целей итерации.

#ifndef __TREES_HXX
#define __TREES_HXX
#include <cstdlib>  // For NULL
#include <algorithm> // for std::max

// Node class definition. These nodes are to be used for any
// tree where the structure is
//    node
//     /
// left  right
//  /    /
//
// etc., basically two children.
template <typename T>
class Node
{
  public:
    T data_;
    Node<T>* left_;
    Node<T>* right_;
    Node<T>* parent_; // Needed for iterators

    explicit Node(T const&);
    Node(Node<T> const&);
};

template <typename T>
class BinaryTree
{
  protected:
    typedef Node<T>* node_t;
    size_t tree_size;

  public:
    typedef T value_type;

    explicit BinaryTree();
    explicit BinaryTree(T const&);
    ~BinaryTree();

    virtual node_t insert(node_t&, T) = 0;
    virtual T& lookup(node_t const&, T const&) const = 0;
    inline virtual size_t size() const;
    inline virtual size_t depth(node_t const&) const;
    inline bool empty() const;
    inline void clear(node_t);

    node_t root;
};

Это основное расширение бинарного дерева нашего абстрактного класса, в основном это (будет) BST. Например, почему я нужны итераторы, посмотрите на определение функции lookup. Он должен возвращать итератор в узел, где находится материал.

/* Implementation of our Binary Tree is in
 * this file. The node class is in Trees.hxx
 * because it's intended to be a general class.
 */

 #ifndef __BINARY_TREE_HXX
 #define __BINARY_TREE_HXX

 #include "Trees.hxx"


 template <typename T>
 class BiTree : public BinaryTree<T>
 {
   private:
     typedef typename BinaryTree<T>::node_t node_t;

   public:
     typedef typename BinaryTree<T>::value_type value_type;

     BiTree() : BinaryTree<T>()
     {
     }

     BiTree(T const& data) : BinaryTree<T>(data)
     {
     }

     node_t insert(node_t&, T);
     T& lookup(node_t const&, T const&) const; // Note: This should return an iterator to the node where the stuff is found
 };

Я думаю, что это все - спасибо за ваше время! Если вам нужна дополнительная информация, дайте мне знать.

2 ответов


1. Жирные итераторы против худых итераторов

существует два возможных способа реализации обхода дерева. Вы можете также:

  • имеют узлы, которые просто указывают на их "детей", и итераторы, которые держат стек (таким образом,жир итераторы)
  • имеют узлы, которые имеют Родительский указатель (например, ваш), и итераторы, которые являются просто указателями на данный узел (lean итераторы)

это компромисс дизайна, реализаторы STL обычно идут бережливым путем, потому что итераторы (в STL) должны быть дешевыми для копирования.

2. Легкие итераторы против итераторов с нуля

есть также несколько способов реализации итераторов:

  • С нуля: вы делаете все сами, что включает в себя определение typedef, все перегрузки оператора и т. д...
  • легко: вы используйте Boost.Итератор, чтобы реализовать как можно меньше кода самостоятельно

Я в основном считаю наследование от std::iterator как ситуация "с нуля", так как она обеспечивает всего лишь 5 typedef...

выбор того или другого действительно зависит от вашей ситуации:

  • для обучения цели, я бы рекомендовал идти" с нуля " путь несколько раз
  • для производственной цели, я рекомендовал бы пойти" с нуля " путь (наследование от Boost не экономит много, но это усложняет отладку сеансов / дампов памяти, по крайней мере, с gdb, потому что gdb предоставляет базовые классы)
  • для быстрого тестирования я бы рекомендовал пойти "легким" путем

обратите внимание, что вы можете оказаться в странной ситуации, когда ваш итератор не может быть построен поверх Boost.Итератор, в этом случае у вас не будет выбора, кроме как построить его самостоятельно.

3. Const и non-const итераторы

это, пожалуй, главное.

если только для этого, стоит посмотреть на Boost.Итератор, потому что они выставляют технику для реализации один итератор (шаблон), который будет охватывать и случаях.

посмотреть Пример на Итератор Адаптер:

template <class Value>
class node_iter
  : public boost::iterator_adaptor<
        node_iter<Value>                // Derived
      , Value*                          // Base
      , boost::use_default              // Value
      , boost::forward_traversal_tag    // CategoryOrTraversal
    >
{
 private:
    struct enabler {};  // a private type avoids misuse

 public:
    node_iter()
      : node_iter::iterator_adaptor_(0) {}

    explicit node_iter(Value* p)
      : node_iter::iterator_adaptor_(p) {}

    /// !!! Highlight !!!
    template <class OtherValue>
    node_iter(
        node_iter<OtherValue> const& other
      , typename boost::enable_if<
            boost::is_convertible<OtherValue*,Value*>
          , enabler
        >::type = enabler()
    )
      : node_iter::iterator_adaptor_(other.base()) {}

 private:
    friend class boost::iterator_core_access;
    void increment() { this->base_reference() = this->base()->next(); }
};

3-й конструктор является ключевым моментом для получения пара const и неconst итераторы с автоматическим преобразованием из const неconst без возможности обратного преобразования.

что бы вы ни делали, используйте тот же трюк: templatize a BaseIterator on Value, и предоставить два typedefs:typedef BaseIterator<Value> iterator и typedef BaseIterator<Value const> const_iterator.


один из способов реализовать это-иметь стек в итераторе, который отслеживает родительские узлы. Каждый раз, когда вы приходите к узлу, это не лист, нажмите его в стек и перейдите к следующему узлу в порядке поиска. Как только вы нажмете лист, обработайте его, а затем вернитесь к узлу поверх стека. Повторяйте ad nausium, пока не посетите все.