迭代地图

发布于 2024-09-12 12:05:07 字数 287 浏览 5 评论 0原文

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

In this question I'm not asking how to do it but HOW IS IT DONE.
I'm trying (as an excersise) implement simple map and although I do not have problems with implementing links and they behavior (how to find next place to insert new link etc.) I'm stuck with the problem how to implement iterating over a map. When you think about it and look at std::map this map is able to return begin and end iterator. How? Especially end?
If map is a tree how can you say which branch of this map is an end? I just do not understand it. An how to iterate over a map? Starting from the top of the tree and then what? Go and list everything on the left? But those nodes on the left have also links to the right. I really don't know. I will be really glad if someone could explain it to me or give me a link so I could read about it.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

莫多说 2024-09-19 12:05:07

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_ 或类似的东西)。

A map is implemented using a binary search tree. To meet the complexity requirements it has to be a self-balancing tree, so a red-black tree is usually used, but that doesn't affect how you iterate over the tree.

To read the elements out of a binary search tree in order from least to greatest, you need to perform an in-order traversal of the tree. The recursive implementation is quite simple but isn't really practical for use in an iterator (the iterator would have to maintain a stack internally, which would make it relatively expensive to copy).

You can implement an iterative in-order traversal. This is an implementation taken from a library of tree containers I wrote a while ago. NodePointerT is a pointer to a node, where the node has left_, right_, and parent_ pointers of type 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;
}

To find the first node for the begin iterator, you need to find the leftmost node in the tree. Starting at the root node, follow the left child pointer until you encounter a node that has no left child: this is the first node.

For an end iterator, you can set the node pointer to point to the root node or to the last node in the tree and then keep a flag in the iterator indicating that it is an end iterator (is_end_ or something like that).

自我难过 2024-09-19 12:05:07

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

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

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

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

The representation of your map's iterator is totally up to you. I think it should suffice to use a single wrapped pointer to a node. E.g.:

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

Or something similar. Now, mymap::begin() could return such instance of the iterator that n would point to the leftmost node. mymap::end() could return instance with n pointing to root probably or some other special node from which it is still possible to get back to rightmost node so that it could satisfy bidirectional iteration from end iterator.

The operation of moving between the nodes (operators++() and operator--(), etc.) are about traversing the tree from smaller to bigger values or vice versa. Operation that you probably have already implemented during insertion operation implementation.

宛菡 2024-09-19 12:05:07

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

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;

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

当您调用其 find() 方法或使用运算符[] 时,std::map 会在内部使用二分搜索,但您不需要直接访问树表示。

For sorting purposes, a map behaves like a sorted key/value container (a.k.a. a dictionary); you can think of it as a sorted collection of key/value pairs, and this is exactly what you get when you query for an iterator. Observe:

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;

Just like any other iterator type, the map iterator behaves like a pointer to a collection element, and for map, this is a std::pair, where first maps to the key and second maps to the value.

std::map uses a binary search internally when you call its find() method or use operator[], but you shouldn't ever need to access the tree representation directly.

向地狱狂奔 2024-09-19 12:05:07

您可能会忽略的一个重要技巧是 end() 迭代器不需要指向任何内容。它可以是 NULL 或任何其他特殊值。

当迭代器越过映射末尾时,++ 运算符将迭代器设置为相同的特殊值。然后一切正常。

要实现++,您可能需要在每个节点中保留下一个/上一个指针,或者您可以通过将刚刚离开的节点与父级最右边的节点进行比较,沿着树向上查找下一个节点节点来查看您是否需要走到那个父节点等。

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

One big trick you may be missing is that the end() iterator does not need to point to anything. It can be NULL or any other special value.

The ++ operator sets an iterator to the same special value when it goes past the end of the map. Then everything works.

To implement ++ you might need to keep next/prev pointers in each node, or you could walk back up the tree to find the next node by comparing the node you just left to the parent's right-most node to see if you need to walk to that parent's node, etc.

Don't forget that the iterators to a map should stay valid during insert/erase operations (as long as you didn't erase the iterator's current node).

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文