我对二进制搜索树和二进制堆上的find_min操作的运行时有些困惑。我知道在二进制堆中返回min是一个O(1)操作。我也理解为什么理论上,返回二进制搜索树中的最小元素是O(log(N))操作。令我惊讶的是,当我读到C ++ STL中的数据结构时,文档声明将迭代器返回到映射中的第一个元素(与返回最小元素相同)是在恒定时间内发生的!难道这不能以对数时间返回吗?我需要有人帮助我理解C ++正在做什么,以便在不变的时间内返回。因为那时,在C ++中真正使用二进制堆是没有意义的,因此地图数据结构将支持在常量时间中检索min和max,在O(log(N))中删除和搜索并保持一切排序。这意味着数据结构具有BST和二进制堆的优点,所有这些都捆绑在一起!
我与一位采访者(不是真正的争论)就这个问题进行了争论,但我试图向他解释,在C ++中,从C ++(这是一种自我平衡的二元搜索树)中的map中返回min和max恒定时间。他感到困惑,不停地说我错了,二进制堆是要走的路。非常感谢澄清
答案 0 :(得分:6)
通过在地图的头部结构中存储对RB树的最左侧和最右侧节点的引用来实现最小值和最大值的恒定时间查找。以下是评论f rom the source code of the RB-tree,这是一个模板,可以从中导出std::set
,std::map
和std::multimap
的实现:
标头单元不仅与根连接,而且还与树的最左边节点保持链接,以启用常量时间
begin()
,并启用树的最右边节点,以启用线性时间性能与通用集算法(set_union
等)一起使用
这里的权衡是需要维护这些指针,因此插入和删除操作将有另一个“内务操作”。但是,插入和删除已经在对数时间内完成,因此维护这些指针没有额外的渐近成本。
答案 1 :(得分:2)
至少在典型的实现中,std::set
(和std::map
)将实现为线程二叉树 1 。换句话说,每个节点不仅包含指向其(最多)两个子节点的指针,还包含按顺序指向上一节点和下一节点的指针。然后,set
类本身不仅指向树的根,还指向节点的线程列表的开头和结尾。
要按键搜索节点,请使用常规二进制指针。要按顺序遍历树,使用线程指针。
与二进制堆相比,这确实有许多缺点。最明显的是它为每个数据项存储了四个指针,其中二进制堆只能存储数据,没有指针(节点之间的关系隐含在数据的位置)。在极端情况下(例如,std::set<char>
),最终可能会使用 lot 更多的存储指针而不是实际关注的数据(例如,在64位系统上)可以最终得到4个64位的指针,以存储每个8位字符)。这可能导致缓存利用率降低,而这反过来又会影响速度。
此外,每个节点通常都会单独分配,这可能会严重降低引用的局部性,再次损害缓存使用率并进一步降低速度。
因此,即使线程树可以找到最小值或最大值,或者遍历O(1)中的下一个或上一个节点,并搜索O(log N)中的任何给定项,常量也可以是高于堆一样。根据所存储项目的大小,使用的总存储量可能远远大于堆积(最坏的情况显然是每个节点中只存储少量数据)。
<子> 1.应用了一些平衡算法 - 通常是红黑色,但有时是AVL树或B树。可以使用任何数量的其他平衡树(例如,α平衡树,k邻域树,二叉b树,一般平衡树)。 子>
答案 2 :(得分:-1)
我不是地图专家,但是返回地图的第一个元素将被视为各种各样的“根”。总有一个指向它的指针,所以它的查找时间是即时的。同样适用于BSTree,因为它显然有一个根节点,然后有两个节点关闭它等等(顺便说一句,我会考虑使用AVL树,因为最坏情况下的查找时间比BSTree好得多) )。
O(log(N))通常仅用于估计最坏情况。因此,如果你有一个完全不平衡的BSTree,你实际上会有O(N),所以如果你搜索最后一个节点,你必须对每个节点进行比较。
虽然地图不同于自平衡树,但我不太确定你的最后一句话,那些叫做AVL树(或者就是我所教的)。地图使用“键”以特定方式组织对象。通过将数据序列化为数字找到密钥,并且数字大部分放在列表中。