【发布时间】:2010-02-14 16:02:03
【问题描述】:
我多次访问二叉树的最小元素。哪些实现允许我在恒定时间中访问最小元素,而不是O(log n)?
【问题讨论】:
-
您可能想要添加有关树更改频率以及是否可以忍受插入/删除成本高昂(重建缓存信息)的信息。
标签: java algorithm data-structures complexity-theory binary-tree
我多次访问二叉树的最小元素。哪些实现允许我在恒定时间中访问最小元素,而不是O(log n)?
【问题讨论】:
标签: java algorithm data-structures complexity-theory binary-tree
根据您的其他要求,min-heap 可能是您正在寻找的。它为您提供了最小元素的恒定时间检索。
但是,您不能像使用简单的二叉搜索树一样轻松地执行其他一些操作,例如确定值是否在树中。您可以查看splay trees,这是一种自平衡二叉树,可提高对最近访问元素的访问时间。
【讨论】:
在 O(log n) 中找到它一次,然后将要添加的新值与这个缓存的最小元素进行比较。
UPD:关于如果您删除最小元素,这将如何工作。您只需要再花 O(log n) 一次并找到新的。
假设您的树中有 10 000 000 000 000 个整数。每个元素在内存中需要 4 个字节。在这种情况下,您的所有树都需要大约 40 TB 的硬盘空间。在这棵巨大的树中搜索最小元素应该花费的时间 O (log n) 大约是 43 次操作。当然,这不是最简单的操作,但无论如何,即使对于 20 年的处理器来说,它也几乎什么都不是。
当然,如果这是一个现实世界的问题,这是实际的。如果出于某些目的(可能是学术目的)您需要真正的 O(1) 算法,那么我不确定我的方法是否可以在不使用额外内存的情况下为您提供这样的性能。
【讨论】:
这听起来可能很愚蠢,但如果您主要访问最小元素,并且不要过多地更改树,那么在添加/删除(在任何树上)时维护指向最小元素的指针可能是最好的解决方案。
【讨论】:
/usr/src/linux/kernel/sched_fair.c 中的 cfs_rq.rb_leftmost。
遍历树总是 O(log n)。您是否自己编写了树实现?您始终可以简单地将对当前最低值元素的引用与数据结构一起存储,并在添加/删除节点时保持更新。 (如果您没有编写树,您也可以通过将树实现包装在您自己的包装器对象中来执行此操作。)
【讨论】:
TAOCP 中有一个实现,它使用非完整节点中的“备用”指针按顺序完成沿节点的双链表(我现在不记得细节,但我想象你每个方向都必须有一个“has_child”标志才能使其工作)。
有了它和一个起始指针,你可以在 O(1) 时间内获得起始元素。
此解决方案并不比缓存最小值更快或更有效。
【讨论】:
如果 最小元素 是指 具有最小值的元素,那么您可以使用 TreeSet 和自定义 Comparator 将项目排序为正确的存储顺序单个元素,然后只需调用 SortedSet#first() 或 #last() 即可尽可能高效地获取最大/最小值。
请注意,与其他 Sets/Lists 相比,向 TreeSet 插入新项目稍慢,但如果您没有大量不断变化的元素,那么这应该不是问题。
【讨论】:
O(log n)。
Comparable,你甚至不需要Comparator。
如果您可以使用一点内存,听起来组合集合可能适合您。
例如,您要查找的内容听起来很像链表。您始终可以找到最小元素,但插入或查找任意节点可能需要更长的时间,因为您必须进行查找 O(n)
如果您将链表和树结合起来,您可能会得到两全其美的效果。要查找获取/插入/删除操作的项目,您将使用树来查找元素。元素的“持有人”节点必须有办法从树跨越到链表以进行删除操作。此外,链表必须是双向链表。
所以我认为获得最小的项目将是 O(1),任何任意查找/删除都将是 O(logN)——我认为即使是插入也是 O(logN),因为您可以找到将其放入的位置树,查看前一个元素并从那里交叉到您的链表节点,然后添加“下一个”。
嗯,这开始看起来像是一个非常有用的数据结构,可能在内存上有点浪费,但我认为任何操作都不会比 O(logN) 更糟糕,除非你必须重新平衡树。
【讨论】:
如果你将二叉树升级/“upcomplex”到threaded binary tree,那么你可以获得O(1)个第一个和最后一个元素。
您基本上保留了对当前第一个和最后一个节点的引用。
在插入之后,如果 first 的 previous 不为 null,则首先更新。最后也是一样。
每当你删除时,你首先检查被删除的节点是第一个还是最后一个。并适当地更新最先存储的内容。
【讨论】: