简体   繁体   English

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

[英]Implementing an iterator over binary (or arbitrary) tree using C++ 11

I would like to create an iterator over the binary tree so as to be able to use range-based for loop. 我想在二叉树上创建一个迭代器,以便能够使用基于范围的for循环。 I understand I ought to implement the begin() and end() function first. 我知道我应该首先实现begin()和end()函数。

Begin should probably point to the root. 开始应该指向根。 According to the specification, however, the end() functions returns "the element following the last valid element". 但是,根据规范,end()函数返回“最后一个有效元素后面的元素”。 Which element (node) is that? 哪个元素(节点)是那个? Would it not be illegal to point to some "invalid" place? 指向某个“无效”的地方不是非法的吗?

The other thing is the operator++. 另一件事是运算符++。 What is the best way to return "next" element in tree? 在树中返回“next”元素的最佳方法是什么? I just need some advice to begin with this programming. 我只需要一些建议就可以从这个编程开始。


I would like to expand/augment my question*. 我想扩大/扩充我的问题*。 What if I wanted to iterate over a tree with an arbitrary arity? 如果我想迭代一个任意arity的树怎么办? Let each node have a vector of children and let begin() point to the "real" root. 让每个节点都有一个子节点向量,让begin()指向“真正的”根节点。 I would probably have to implement a queue (for breadth-first) inside the iterator class to store the unique_ptr's to nodes, right? 我可能不得不在迭代器类中实现一个队列(forstth-first)来将unique_ptr存储到节点,对吗? Then, when the queue is empty I would know that I have passed all nodes and thus should return TreeIterator(nullptr) when oprator++() is called. 然后,当队列为空时,我知道我已经传递了所有节点,因此在调用oprator ++()时应该返回TreeIterator(nullptr)。 Does it make sense? 是否有意义? I want it as simple as possible and only forward iteration. 我希望它尽可能简单,只需要前向迭代。

*Or should I create a new thread? *或者我应该创建一个新线程?

Where your begin() should point to pretty much depends on the order in which you want to traverse your tree. 你的begin()应该指向的地方几乎取决于你想要遍历你的树的顺序。 Using the root may be sensible, eg, for a breadth first walk of the tree. 使用根可能是明智的,例如,对于树的广度第一次行走。 The end() doesn't really sit on a tree node: this position is accessed and indicates that the end of the sequence is reached. end()实际上并不位于树节点上:访问此位置并指示已到达序列的末尾。 Whether it indicates anything related to the tree somewhat depends on what sort of iteration you want to support: when supporting only forward iteration it can just indicate the end. 它是否表示与树相关的任何内容在某种程度上取决于您想要支持的迭代类型:当仅支持前向迭代时,它只能指示结束。 When also supporting bidirectional iteration, it needs to know how to find the node right before the end. 当还支持双向迭代时,它需要知道如何在结束之前找到节点。

In any case, the place pointed to isn't really accessed and you need a suitable indicator. 在任何情况下,指向的地方都没有真正访问过,你需要一个合适的指标。 For a forward iteration only iterator end() could just return an iterator pointing to null and when you move on from the last node you just set the iterator's pointer to null as well: equality comparing the two pointers would yield true , indicating that you have reached the end. 对于正向迭代,只有迭代器end()可以返回指向null的迭代器,当你从最后一个节点继续时,你只需将迭代器的指针设置为null:比较两个指针的相等性将产生true ,表明你有走到了尽头。 When wanting to support bidirectional iteration you'll need some sort of link record which can be used to navigate to the previous node but which doesn't need to store a value. 当想要支持双向迭代时,您需要某种链接记录,可用于导航到上一个节点,但不需要存储值。

The ordered associated containers ( std::map<K, V> , std:set<V> , etc.) are internally implemented as some sort of tree (eg, a Red/Black-tree). 有序关联容器( std::map<K, V>std:set<V>等)在内部实现为某种树(例如,红/黑树)。 The begin() iterator starts with the left-most node and the end() iterator refers to the position after the right-most node. begin()迭代器以最左边的节点开始,而end()迭代器引用最右边节点之后的位置。 The operator++() just finds the next node to the right of the current: operator++()只找到当前右边的下一个节点:

  • if the iterator sits on a node without a right child node, it walks along the chain of parents until it finds a parent reaching its child via the left branch of the tree 如果迭代器位于没有右子节点的节点上,它会沿着父节点链行走,直到找到父节点通过树的左侧分支到达其子节点
  • if it sits on a node with a right child node it walks to the child and then down the sequence of left children of this child (if any) to find the left-most child in the right subtree. 如果它位于具有右子节点的节点上,则它会走向该子节点,然后向下移动该子节点的左子节点序列(如果有的话),以找到右子树中最左侧的子节点。

Obviously, if you don't walk your tree from left to right but rather, eg, from top to bottom, you'll need a different algorithm. 显然,如果你不是从左到右走树,而是从上到下走你的树,你需要一个不同的算法。 The easiest approach for me is to draw a tree on a piece of paper and see how to get to the next node. 对我来说最简单的方法是在一张纸上画一棵树,看看如何到达下一个节点。

If you haven't implemented a data structure using your own iterators I'd recommend trying things out on a simple sequential data structure, eg, a list: There it is pretty obvious how to reach the next node and when the end is reached. 如果您还没有使用自己的迭代器实现数据结构,我建议您尝试使用简单的顺序数据结构,例如列表:在那里,如何到达下一个节点以及何时到达终点是非常明显的。 Once the general iteration principle is clear, creating a tree is just a matter of getting the navigation right. 一旦通用迭代原理明确,创建树只是导航正确的问题。

Look at SGI implementation of RBTree (this is the base for std::set / std::map ... containers). 看看RBTree的SGI实现(这是std::set / std::map ...容器的基础)。

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

You will see that begin() is the leftmost node. 您将看到begin()是最左侧的节点。

You will see that end() is a special "empty" node header which is the super root - I mean a real root (preset only if the tree is not empty) is its child node. 你会看到end()是一个特殊的“空”节点头 ,它是超级根 - 我的意思是一个真正的根(仅当树不为空时预设)是它的子节点。

operator ++ is to go to right child and then find this child leftmost node. operator ++是转到右边的孩子,然后找到这个孩子最左边的节点。 If such child does not exist - we go to left parent of the rightmost parent node. 如果这样的子节点不存在 - 我们转到最右边父节点的左侧父节点。 As in this example (red lines are skip move, blue ones ends are the given steps of iteration): 如在这个例子中(红线是跳过移动,蓝色端是迭代的给定步骤):

在此输入图像描述

The code copied from SGI: 从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;
    }
  }

When a tree is empty - begin() is leftmost of header - so it is header itself - end() is also header - so begin() == end() . 当一个树为空时 - begin()是最左边的标题 - 所以它是标题本身 - end()也是标题 - 所以begin() == end() Remember - your iteration scheme must match this condition begin() == end() for empty trees. 请记住 - 您的迭代方案必须匹配此条件begin() == end()为空树。

This seems to be very smart iteration scheme. 这似乎是非常智能的迭代方案。

Of course you can define you own scheme - but the lesson learned is to have special node for end() purpose. 当然,您可以定义自己的方案 - 但学到的经验是为end()目的设置特殊节点。

An iterator for a tree is going to be more than just a pointer, although it will likely contain a pointer: 树的迭代器不仅仅是一个指针,尽管它可能包含一个指针:

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) { ... }

You can make your end() function return something special, like TreeIterator(nullptr) 你可以让你的end()函数返回一些特殊的东西,比如TreeIterator(nullptr)

What your begin iterator points to will depend on the type of traversal that you want. 您的开始迭代器指向的内容取决于您想要的遍历类型。 If you are doing breadth-first traversal, then starting at the root makes sense. 如果你正在进行广度优先遍历,那么从根开始是有道理的。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM