我有BST的基本(没有随机化,订购等)实现。我想添加迭代器实现并使BST适用于基于范围的for循环。 所以我需要begin(),end()成员函数和迭代器递增。
我理解begin()应该做什么 - 将迭代器返回到最左下角的节点,this thread讨论了遍历BST的不同可能性(=递增迭代器)
但是end()应该给迭代器提供一个过去的最后一个元素。这是实际的问题,我不明白,在BST的背景下,这是什么意思?
答案 0 :(得分:3)
结束迭代器不一定必须是最后一个元素的一个(对于向量是有意义的,但对于树来说则更少,例如。)。它必须只是一个迭代器,可以清楚地识别为不是用于指示数据结构的末尾到达的有效迭代器。
实际上,这可以通过多种方式完成,具体取决于迭代器如何引用它所指向的内容。如果它使用指向树节点的指针,例如,则可以将空指针用于结束迭代器。
答案 1 :(得分:1)
一个非常简单的方案,使用两个额外的指针值内存是简单地覆盖BST顶部的双向链接圆形列表。然后,您的end()
迭代器只指向一个标记节点。它还使您的迭代器递增/递减非常简单。
BST::iterator &
BST::iterator::operator++() {
n = n->next;
return *this;
}
等。请注意,使用这样的标记意味着end
迭代器不需要特殊处理。您可以减少它并获得完全正确的行为。
答案 2 :(得分:0)
尽管我的评论,Sander De Dycker有正确的想法。我有另一种思考方式。
支持迭代器的所有容器都具有逻辑顺序。对于vector
,排序基于插入的完成方式 - 索引/下标排序。对于map
和set
,它基于密钥排序。对于multimap
和multiset
,它们都是两者兼而有之。对于unordered_map
等,声称非常脆弱,但我仍然可以争论散列算法和碰撞处理。
在逻辑排序中,您可以引用有序元素,但有时引用每个元素之间的边界是有意义的。逻辑上(在某些情况下甚至是实现)这可以非常方便地进行...
| | | | | | | | |
| +-+ | +-+ | +-+ | +-+ | +-+ | +-+ | +-+ | +-+ |
| |0| | |1| | |2| | |3| | |4| | |5| | |6| | |7| |
| +-+ | +-+ | +-+ | +-+ | +-+ | +-+ | +-+ | +-+ |
| | | | | | | | |
0 1 2 3 4 5 6 7 8
您决定零“绑定”的位置与零项目的位置无关,但您总是得到一个简单的加法/减法关系。如果最小边界的编号与最小元素的编号相同,则最后一个边界的编号比最后一个元素多一个。因此,end
作为最后一个元素之后的一个。
在二叉树实现中,每个节点都可以被认为有两个边界 - 元素的任一边。在此方案中,除begin
和end
之外的每个绑定都会出现两次。您可以使用元素0的RHS或LHS或元素1来表示绑定1.因此原则上您可以使用节点指针和标志。不过,对于大多数边界而言,您可能会选择最方便的一种 - 您不仅可以选择右边界,还可以引用您想要取消引用时要看到的元素。这意味着只有在引用end
时才会设置标志,在这种情况下,您不应该支持取消引用。
以下通过这个逻辑告诉你,你并不需要遵循这个逻辑,尽管我认为它仍然是一个有用的心智模型。您真正需要的只是end
的可识别表示。也许对于该表示来说,包括指向最终指针的指针(作为例如递减迭代器的起点)是有用的。也许在某些情况下,在内部使用伪迭代器可以很方便地将两个等效边界识别为不同。
类似但略有不同的模型和选择出现在考虑例如多路树,其中每个节点都包含一个元素数组。
基本上我认为精神上认识到绑定位置是独特但与项目位置相关是有用的,但心理模型不应该限制你的实现选择 - 它可能激发替代方案,但它只是一个心理模型。