设计一个支持n元素集上的以下操作的数据结构:
O(lg n)
O(1)
时间内删除最多值。请注意,删除max在常规堆中需要O(lg n)
。我的方法:
我决定保留一个单独的数组,它将跟踪将超越根的潜在后继者(这是最大堆中的最大值);一旦删除。所以如果你删除了最大值,在O(1)
时间我将查看我的数组以找到下一个合适的后继者(我假设我将智能地设置)。
有人有更好的方法吗?尽量坚持使用堆。这不是一个家庭工作的问题,我正准备接受采访,而且它来自Skiena的书。
答案 0 :(得分:4)
我建议您使用skip list,因为它最容易实现我能想到的数据结构,它支持具有所请求复杂性的操作。 Fibonacci heap也将完成这项工作,但实施起来却很难。
编辑:我接受了斐波那契堆的回复 - 它支持常量插入和O(log(n))
delete_min,这与你想要的相反。对不起。
答案 1 :(得分:3)
您的要求非常具体 - 您只对以下两项操作感兴趣:插入 O (lg n )和deleteMin O (1)。
已知的heap structures都不满足您的特定约束。相反,最好的结构(虽然理论上 - Galactic structures就像有人会称之为)Brodal Queue,它执行 O 中的所有堆操作(1)最坏情况时间,除了deleteMin,它仍然需要 O (lg n )最坏情况时间。所有其他已知结构都没有好转。
由于您只对这两个操作感兴趣,因此您可以使用只能很好地处理这两个操作的自定义结构,因为您不必担心减少键,合并等更普遍的堆结构必须担心。
维护双重结构 DL ,包含:
此外,在 L 中的每个条目与 D 或 T 中的相应条目之间保持链接,反之亦然。此外,您需要为 D 的每个条目保留一点,指示它是否已在 L 中删除。为了稍后在 D 和 L 之间进行大规模同步,您可能还需要跟踪删除次数 d ,自上次同步以来 L 。您可以在以下不变量后执行同步:
违反了。这样,同步时间在 n 中保持线性,您可以始终保证| T |和 d 在可管理的范围内。
因此,要将新元素 e 插入 DL ,首先要在 D 中进行查找( e )为其继任者(前任)和另一个搜索其在 T 中的继任者并抓住更大的继承者(较小的前身)的引用并使用它来插入 L 并将 e 添加到 T 并维护参考。插入 e 后,我们检查是否违反了Invariant 1。如果是这样,我们会触发同步。
同步基本上合并 T 和 D 的内容,同时将标记为已删除的元素移除到新的 D 结构中。这可以在| T |中按时间线性完成+ | D | = O(n)。在另一个线性时间内,可以更新 L 和 D 之间的引用。该批量同步的成本可以在插入和删除上分配(摊销)。因此,这些成本仅为摊销成本。
要执行deleteMin,只需删除 L 的头部,并使用其 D 的反向链接将 D 中的相应条目标记为删除并增加 d 。
观察1 :请注意,deleteMin将始终删除最小元素,因为 L 始终是最新的。
观察2 : D 并非始终与 L 同步。它可能有一些已删除的元素(如此标记)和一些插入的元素只能在 T 中找到。
根据观察2,我们需要在某个时刻安排 D 与 L 的同步,以便维护 O (lg n )查找并插入费用。每当违反Invariant 1时都会这样做。
我忽略了一个令人讨厌的问题:如何在对数时间内插入 T ,同时仍能在同步期间进行线性扫描。只有某种平衡的树才能实现这一目标。我已经想到了将T的大小限制为对数大小的想法,但是当进行足够数量的插入以触发同步时,这会增加同步的摊销成本。似乎某种线程平衡树甚至跳过列表都应该有帮助。
随意批评并建议改进,因为我没有在这里证明或实施所有断言。
更新:正如@delnan所指出的,这些费用是摊销的。我已更新说明以突出显示此事实,并为了清晰起见进行了修订。或许,通过一些诡计可以消除摊销,但在这种情况下我们最终会得到另一个银河系结构。
答案 2 :(得分:0)
最简单的解决方案是使用具有插入时间O(logn)的skiplist维护始终排序的集合,然后您可以删除O(1)中的max或min元素,因为它可以是列表的头部或尾部。 / p>