【问题标题】:Binary tree to get minimum element in O(1)在 O(1) 中获取最小元素的二叉树
【发布时间】:2010-02-14 16:02:03
【问题描述】:

我多次访问二叉树的最小元素。哪些实现允许我在恒定时间中访问最小元素,而不是O(log n)

【问题讨论】:

  • 您可能想要添加有关树更改频率以及是否可以忍受插入/删除成本高昂(重建缓存信息)的信息。

标签: java algorithm data-structures complexity-theory binary-tree


【解决方案1】:

根据您的其他要求,min-heap 可能是您正在寻找的。它为您提供了最小元素的恒定时间检索。

但是,您不能像使用简单的二叉搜索树一样轻松地执行其他一些操作,例如确定值是否在树中。您可以查看splay trees,这是一种自平衡二叉树,可提高对最近访问元素的访问时间。

【讨论】:

  • 它需要保持元素的完整顺序以用于其他目的;不过,我经常访问顶级元素。
  • 如果您经常访问的元素实际上是树的“顶部”或根(而不是最小 value 元素),那么直接访问它应该是 O(1)已经因为不需要遍历。
  • @Ben:我认为 Rudiger 的意思是顶部元素,如果用堆实现(我的回答最初只提到堆)
  • @Rudiger:除了树之外只维护最小堆应该适用于任意插入/删除。这不是一个选择吗?
  • @Moron:这是一个好主意,如果您可以节省额外的结构内存开销,那么它可以在某些情况下工作。
【解决方案2】:

在 O(log n) 中找到它一次,然后将要添加的新值与这个缓存的最小元素进行比较。

UPD:关于如果您删除最小元素,这将如何工作。您只需要再花 O(log n) 一次并找到新的。

假设您的树中有 10 000 000 000 000 个整数。每个元素在内存中需要 4 个字节。在这种情况下,您的所有树都需要大约 40 TB 的硬盘空间。在这棵巨大的树中搜索最小元素应该花费的时间 O (log n) 大约是 43 次操作。当然,这不是最简单的操作,但无论如何,即使对于 20 年的处理器来说,它也几乎什么都不是。

当然,如果这是一个现实世界的问题,这是实际的。如果出于某些目的(可能是学术目的)您需要真正的 O(1) 算法,那么我不确定我的方法是否可以在不使用额外内存的情况下为您提供这样的性能。

【讨论】:

  • 如果最小元素从树中移除,你需要找到新的最小值。这会影响这种方法吗?
  • 如果你可以控制树的实现:当你从树中删除最小元素时会遇到新的最小元素,所以你可以在 O(1) 额外的时间内更新最小元素。 (当然,实际移除最小元素仍然需要花费 O(log n)。)
  • 每次插入/删除时只需更新缓存的最小值。 FindMin 仍然是 O(1) 并且插入/删除仍然是 O(log n),假设 RB 或一些类似的平衡树实现。如果树允许您遍历它(如 dfs),则无需弄乱内部实现。
  • 问题在于,在许多现实世界的场景中,第一个/最后一个元素可能比其他元素更频繁地被删除。
【解决方案3】:

这听起来可能很愚蠢,但如果您主要访问最小元素,并且不要过多地更改树,那么在添加/删除(在任何树上)时维护指向最小元素的指针可能是最好的解决方案。

【讨论】:

  • 请注意,这只有在您是实现树的人时才有效。如果您采用给定的树(具有给定的接口),则可能无法维护指针(但值是......)。
  • 在 BST 中添加/删除节点是 O(log n),因此在同一时间点保持最小指针不会改变算法复杂度。 Linux 内核的 CFS 调度程序为其 rbtree 任务执行此操作,实际上:请参阅 /usr/src/linux/kernel/sched_fair.c 中的 cfs_rq.rb_leftmost
【解决方案4】:

遍历树总是 O(log n)。您是否自己编写了树实现?您始终可以简单地将对当前最低值元素的引用与数据结构一起存储,并在添加/删除节点时保持更新。 (如果您没有编写树,您也可以通过将树实现包装在您自己的包装器对象中来执行此操作。)

【讨论】:

    【解决方案5】:

    TAOCP 中有一个实现,它使用非完整节点中的“备用”指针按顺序完成沿节点的双链表(我现在不记得细节,但我想象你每个方向都必须有一个“has_child”标志才能使其工作)。

    有了它和一个起始指针,你可以在 O(1) 时间内获得起始元素。

    此解决方案并不比缓存最小值更快或更有效。

    【讨论】:

    • @Martinho:就是这样。很好的链接。
    【解决方案6】:

    如果 最小元素 是指 具有最小值的元素,那么您可以使用 TreeSet 和自定义 Comparator 将项目排序为正确的存储顺序单个元素,然后只需调用 SortedSet#first()#last() 即可尽可能高效地获取最大/最小值。

    请注意,与其他 Sets/Lists 相比,向 TreeSet 插入新项目稍慢,但如果您没有大量不断变化的元素,那么这应该不是问题。

    【讨论】:

    • 我很确定这(一棵红黑树)是O(log n)
    • Rudiger 是正确的:TreeSet 是用 TreeMap 实现的(在文档中),TreeMap 是红黑树:java.sun.com/javase/6/docs/api/java/util/TreeMap.html
    • 如果元素已经是Comparable,你甚至不需要Comparator
    【解决方案7】:

    如果您可以使用一点内存,听起来组合集合可能适合您。

    例如,您要查找的内容听起来很像链表。您始终可以找到最小元素,但插入或查找任意节点可能需要更长的时间,因为您必须进行查找 O(n)

    如果您将链表和树结合起来,您可能会得到两全其美的效果。要查找获取/插入/删除操作的项目,您将使用树来查找元素。元素的“持有人”节点必须有办法从树跨越到链表以进行删除操作。此外,链表必须是双向链表。

    所以我认为获得最小的项目将是 O(1),任何任意查找/删除都将是 O(logN)——我认为即使是插入也是 O(logN),因为您可以找到将其放入的位置树,查看前一个元素并从那里交叉到您的链表节点,然后添加“下一个”。

    嗯,这开始看起来像是一个非常有用的数据结构,可能在内存上有点浪费,但我认为任何操作都不会比 O(logN) 更糟糕,除非你必须重新平衡树。

    【讨论】:

      【解决方案8】:

      如果你将二叉树升级/“upcomplex”到threaded binary tree,那么你可以获得O(1)个第一个和最后一个元素。

      您基本上保留了对当前第一个和最后一个节点的引用。

      在插入之后,如果 first 的 previous 不为 null,则首先更新。最后也是一样。

      每当你删除时,你首先检查被删除的节点是第一个还是最后一个。并适当地更新最先存储的内容。

      【讨论】:

        猜你喜欢
        • 2021-03-07
        • 2016-03-18
        • 1970-01-01
        • 1970-01-01
        • 2014-03-05
        • 2018-10-11
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多