【问题标题】:Iterating over a map遍历地图
【发布时间】:2011-03-23 20:25:30
【问题描述】:

在这个问题中,我不是在问如何做,而是在问如何做。
我正在尝试(作为练习)实现简单的地图,虽然我在实现链接和它们的行为方面没有问题(如何找到下一个插入新链接的位置等)我遇到了如何实现迭代的问题一张地图。当您考虑它并查看 std::map 时,此映射能够返回开始和结束迭代器。如何?尤其是结局?
如果地图是一棵树,你怎么能说这张地图的哪个分支是终点?我只是不明白。如何迭代地图?从树顶开始,然后呢?去把左边的所有东西都列出来?但是左边的那些节点也有到右边的链接。我真的不知道。如果有人可以向我解释或给我一个链接以便我可以阅读它,我将非常高兴。

【问题讨论】:

    标签: c++ map iteration


    【解决方案1】:

    map 是使用二叉搜索树实现的。为了满足复杂性要求,它必须是自平衡树,因此通常使用红黑树,但这不会影响您对树的迭代方式。

    要按从小到大的顺序从二叉搜索树中读取元素,您需要执行树的中序遍历。递归实现非常简单,但在迭代器中使用并不实际(迭代器必须在内部维护一个堆栈,这会使其复制成本相对较高)。

    您可以实现迭代的中序遍历。这是从我不久前编写的树容器库中获取的一个实现。 NodePointerT 是一个指向节点的指针,该节点有left_right_parent_ 类型的指针NodePointerT

    // Gets the next node in an in-order traversal of the tree; returns null
    // when the in-order traversal has ended
    template <typename NodePointerT>
    NodePointerT next_inorder_node(NodePointerT n)
    {
        if (!n) { return n; }
    
        // If the node has a right child, we traverse the link to that child
        // then traverse as far to the left as we can:
        if (n->right_)
        {
            n = n->right_;
            while (n->left_) { n = n->left_; }
        }
        // If the node is the left node of its parent, the next node is its
        // parent node:
        else if (n->parent_ && n == n->parent_->left_)
        {
            n = n->parent_;
        }
        // Otherwise, this node is the furthest right in its subtree; we 
        // traverse up through its parents until we find a parent that was a
        // left child of a node.  The next node is that node's parent.  If 
        // we have reached the end, this will set node to null:
        else
        {
            while (n->parent_ && n == n->parent_->right_) { n = n->parent_; }
            n = n->parent_;
        }
        return n;
    }
    

    要找到begin 迭代器的第一个节点,您需要找到树中最左边的节点。从根节点开始,跟随左子指针,直到遇到一个没有左子节点的节点:这是第一个节点。

    对于end 迭代器,您可以将节点指针设置为指向根节点或树中的最后一个节点,然后在迭代器中保留一个标志,表明它是一个结束迭代器(is_end_ 或类似的东西)。

    【讨论】:

      【解决方案2】:

      地图迭代器的表示完全取决于您。我认为使用指向node 的单个包装指针就足够了。例如:

      template <typename T>
      struct mymapiterator
      {
        typename mymap<T>::node * n;
      };
      

      或类似的东西。现在,mymap::begin() 可以返回这样的迭代器实例,n 将指向最左边的节点。 mymap::end() 可以返回实例,n 可能指向 root 或其他一些特殊节点,仍然可以从这些特殊节点返回到最右边的节点,以便它可以满足来自结束迭代器的双向迭代。

      在节点之间移动的操作(operators++()operator--() 等)是关于从较小的值到较大的值遍历树,反之亦然。在插入操作实现过程中您可能已经实现的操作。

      【讨论】:

      • 他可能将遍历实现为递归函数,在执行堆栈和程序计数器中隐式跟踪状态——困难的部分是如何在没有协程的语言中使其显式.
      【解决方案3】:

      出于排序目的,映射的行为类似于已排序的键/值容器(也称为字典);您可以将其视为键/值对的排序集合,这正是您在查询迭代器时得到的。观察:

      map<string, int> my_map;
      my_map["Hello"] = 1;
      my_map["world"] = 2;
      for (map<string, int>::const_iterator i = my_map.begin(); i != my_map.end(); ++i)
          cout << i->first << ": " << i->second << endl;
      

      就像任何其他迭代器类型一样,map 迭代器的行为类似于指向集合元素的指针,对于 map,这是一个 std::pair,其中first 映射到键,second 映射到值.

      std::map 在调用它的 find() 方法或使用 operator[] 时在内部使用二分搜索,但您永远不需要直接访问树表示。

      【讨论】:

      • std::map 始终实现为平衡二叉树
      • @Neil,它通常实现为红黑树(仅部分平衡)。
      • @avakar 我想大多数人会认为 RB 树是平衡的——我同意它们并不完全平衡。
      • 这个我不知道。我会认为它只是一个普通的旧键/值数组,并且二进制搜索将直接在数组上执行。出于迭代的目的,它的行为与普通列表完全一样,所以我的主要观点仍然存在。我对文本进行了编辑,使其在内部实施方面更加正确。
      • 插入数组会太慢;移动以下项目以腾出空间需要线性时间,而map::insert 必须花费对数时间或更短的时间。一棵(相当好的)平衡树可以在对数时间内实现插入和查找。
      【解决方案4】:

      您可能缺少的一个大技巧是end() 迭代器不需要指向任何东西。它可以是 NULL 或任何其他特殊值。

      ++ 操作符在迭代器越过地图末尾时将其设置为相同的特殊值。然后一切正常。

      要实现++,您可能需要在每个节点中保留下一个/上一个指针,或者您可以通过比较您刚刚离开的节点与父节点最右边的节点来返回树以查找下一个节点如果您需要步行到那个父节点等

      不要忘记映射的迭代器在插入/擦除操作期间应该保持有效(只要您没有擦除迭代器的当前节点)。

      【讨论】:

        猜你喜欢
        • 2020-10-23
        • 2011-12-22
        • 1970-01-01
        • 2022-01-13
        • 2015-12-30
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多