使用C ++ 11在二进制(或任意)树上实现迭代器

时间:2012-10-02 03:35:59

标签: c++ tree iterator

我想在二叉树上创建一个迭代器,以便能够使用基于范围的for循环。我知道我应该首先实现begin()和end()函数。

开始应该指向根。但是,根据规范,end()函数返回“最后一个有效元素后面的元素”。哪个元素(节点)是那个?指向某个“无效”的地方不是非法的吗?

另一件事是运算符++。在树中返回“next”元素的最佳方法是什么?我只需要一些建议就可以从这个编程开始。


我想扩大/扩充我的问题*。如果我想迭代一个任意arity的树怎么办?让每个节点都有一个子节点向量,让begin()指向“真正的”根节点。我可能不得不在迭代器类中实现一个队列(forstth-first)来将unique_ptr存储到节点,对吗?然后,当队列为空时,我知道我已经传递了所有节点,因此在调用oprator ++()时应该返回TreeIterator(nullptr)。是否有意义?我希望它尽可能简单,只需要前向迭代。

*或者我应该创建一个新线程吗?

3 个答案:

答案 0 :(得分:9)

begin()指向的位置几乎取决于您想要遍历树的顺序。使用根可能是明智的,例如,对于树的广度第一步。 end()并不真正位于树节点上:访问此位置并指示已到达序列的末尾。它是否表示与树相关的任何内容在某种程度上取决于您想要支持的迭代类型:当仅支持前向迭代时,它只能指示结束。当还支持双向迭代时,它需要知道如何在结束之前找到节点。

在任何情况下,指向的地方都没有真正被访问,你需要一个合适的指标。对于正向迭代,只有迭代器end()才能返回指向null的迭代器,当你从最后一个节点继续时,你只需将迭代器的指针设置为null:比较两个指针的相等性将产生{{1} },表明你已经到了尽头。当想要支持双向迭代时,您需要某种链接记录,可用于导航到上一个节点,但不需要存储值。

有序关联容器(truestd::map<K, V>等)在内部实现为某种树(例如,红/黑树)。 std:set<V>迭代器以最左侧节点开始,begin()迭代器引用最右侧节点之后的位置。 end()只找到当前右边的下一个节点:

  • 如果迭代器位于没有右子节点的节点上,它会沿着父节点链走,直到找到父节点通过树的左侧分支到达其子节点
  • 如果它位于具有正确子节点的节点上,则它会走向该子节点,然后向下移动该子节点的左子节点序列(如果有),以找到右子树中最左侧的子节点。

显然,如果你不是从左到右走树,而是从上到下走你的树,你需要一个不同的算法。对我来说最简单的方法是在一张纸上画一棵树,看看如何到达下一个节点。

如果您还没有使用自己的迭代器实现数据结构,我建议您尝试使用简单的顺序数据结构,例如列表:那里很明显如何到达下一个节点以及何时结束到达了。一旦完成了一般迭代原则,创建树只是导航正确的问题。

答案 1 :(得分:8)

查看RBTree的SGI实现(这是std::set / std::map ...容器的基础。)

http://www.sgi.com/tech/stl/stl_tree.h

您将看到begin()是最左侧的节点。

你会看到end()是一个特殊的“空”节点 header ,它是超级根 - 我的意思是一个真正的根(仅当树不为空时预设)是它的子节点

operator ++是去右边的孩子,然后找到这个孩子最左边的节点。 如果这样的子节点不存在 - 我们转到最右边父节点的左侧父节点。如在此示例中(红线是跳过移动,蓝色端是迭代的给定步骤):

enter image description here

从SGI复制的代码:

  void _M_increment()
  {
    if (_M_node->_M_right != 0) {
      _M_node = _M_node->_M_right;
      while (_M_node->_M_left != 0)
        _M_node = _M_node->_M_left;
    }
    else {
      _Base_ptr __y = _M_node->_M_parent;
      while (_M_node == __y->_M_right) {
        _M_node = __y;
        __y = __y->_M_parent;
      }
      if (_M_node->_M_right != __y)
        _M_node = __y;
    }
  }

当一个树为空时,begin()最左边是标题 - 所以它本身就是标题 - end()也是标题 - 所以begin() == end()。请记住 - 您的迭代方案必须与空树的begin() == end()条件匹配。

这似乎是非常聪明的迭代方案。

当然,您可以定义自己的方案 - 但所学到的经验是为end()目的设置特殊节点。

答案 2 :(得分:0)

树的迭代器不仅仅是一个指针,尽管它可能包含一个指针:

struct TreeIterator {
  TreeIterator(TreeNode *node_ptr) : node_ptr(node_ptr) { }
  TreeIterator &operator++();
  TreeIterator operator++(int);
  bool operator==(const TreeIterator &) const;

  private:
    TreeNode *node_ptr;
};

TreeIterator begin(const Tree &tree) { ... }
TreeIterator end(const Tree &tree) { ... }

你可以让你的end()函数返回一些特殊的东西,比如TreeIterator(nullptr)

您的开始迭代器指向的内容取决于您想要的遍历类型。如果你正在进行广度优先遍历,那么从根开始是有道理的。