【问题标题】:Implementing an iterator over binary (or arbitrary) tree using C++ 11使用 C++ 11 在二叉(或任意)树上实现迭代器
【发布时间】:2012-09-22 22:43:02
【问题描述】:

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

Begin 应该可能指向根。然而,根据规范,end() 函数返回“最后一个有效元素之后的元素”。那是哪个元素(节点)?指向一些“无效”的地方不是违法的吗?

另一件事是operator++。在树中返回“下一个”元素的最佳方法是什么?我只需要一些建议来开始这个编程。


我想扩展/扩充我的问题*。如果我想遍历具有任意数量的树怎么办?让每个节点都有一个子节点向量,并让 begin() 指向“真正的”根。我可能必须在迭代器类中实现一个队列(对于广度优先)来将 unique_ptr 存储到节点,对吧?然后,当队列为空时,我会知道我已经通过了所有节点,因此应该在调用 oprator++() 时返回 TreeIterator(nullptr)。是否有意义?我希望它尽可能简单并且只向前迭代。

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

【问题讨论】:

  • 您的 end() 迭代器最终可能会成为一些并非树中实际节点的哨兵值。

标签: c++ tree iterator


【解决方案1】:

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

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

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

  • 如果迭代器位于没有右子节点的节点上,它会沿着父节点链遍历,直到找到父节点通过树的左分支到达其子节点
  • 如果它位于具有右子节点的节点上,它会走到该子节点,然后沿着该子节点的左子节点序列(如果有)找到右子树中最左边的子节点。

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

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

【讨论】:

    【解决方案2】:

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

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

    你会看到 begin() 是最左边的节点。

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

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

    从 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() 目的设置特殊节点。

    【讨论】:

    • 为什么它必须始终保持 begin()==end()?在this 教程中,作者在数组上实现了一个迭代器,但这个条件不成立。
    • 我的意思是这个条件只适用于空树;)
    • if (_M_node->_M_right != __y) _M_node = __y; 这怎么会返回 false ? __y_M_node 的父级,那么_M_node->_M_right 怎么可能等于__y
    【解决方案3】:

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

    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)

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

    【讨论】:

      猜你喜欢
      • 2011-06-02
      • 2017-05-09
      • 2019-09-29
      • 2012-07-11
      • 2013-11-06
      • 1970-01-01
      • 2015-01-18
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多