【问题标题】:Efficient mass modification of persistent data structures持久数据结构的高效大规模修改
【发布时间】:2011-09-02 19:16:24
【问题描述】:

我了解树通常如何用于修改持久数据结构(创建一个新节点并替换它的所有祖先)。

但是,如果我有一棵由 10,000 个节点组成的树并且我需要修改其中的 1000 个呢?我不想经历并创建 1000 个新根,我只需要一次修改所有内容所产生的一个新根。

例如: 让我们以一个持久二叉树为例。在单个更新节点的情况下,它会进行搜索,直到找到该节点,然后使用修改和旧子节点创建一个新节点,然后创建直到根的新祖先。

在批量更新的情况下,我们可以这样做: 您将一次更新其上的 1000 个节点,而不是仅更新单个节点。

在根节点,当前列表是完整列表。然后将该列表拆分为与左侧节点匹配的列表和与右侧节点匹配的列表。如果没有匹配的孩子之一,不要下降到它。然后下降到左侧节点(假设有匹配项),在其子节点之间拆分其搜索列表,然后继续。当您有一个节点和一个匹配项时,您可以更新它并返回,根据需要替换和更新祖先和其他分支。

即使修改了任意数量的节点,这也只会产生一个新的根。

【问题讨论】:

    标签: data-structures immutability


    【解决方案1】:

    这类“大规模修改”操作有时称为批量更新。当然,细节会有所不同,具体取决于您正在使用的数据结构类型以及您尝试执行的修改类型。

    典型的操作类型可能包括“删除所有满足某些条件的值”或“增加与此列表中所有键关联的值”。通常,这些操作可以一次遍历整个结构,花费 O(n) 时间。

    您似乎担心创建“1000 个新根”所涉及的内存分配。一次执行一个操作的典型分配是 O(k log n),其中 k 是被修改的节点数。在整个结构上执行单次遍历的典型分配是 O(n)。哪个更好取决于 k 和 n。

    在某些情况下,您可以通过特别注意何时发生更改来减少分配量(以更复杂的代码为代价)。例如,如果您有一个返回树的递归算法,您可以修改该算法以返回一棵树以及一个指示是否有任何更改的布尔值。然后,该算法可以在分配新节点之前检查这些布尔值,以查看旧节点是否可以安全地重用。然而,人们通常不会为这种额外的检查而烦恼,除非他们有证据表明额外的内存分配实际上是一个问题。

    【讨论】:

    • 感谢您提供的重要信息。我没有考虑走过整个结构。我在问题中添加了一个示例。在我看来,这个例子的内存占用比 1 by 1 方法要小得多。通过在每个分支处拆分列表,搜索可能类似于 O(k log n),但每个更新的节点(包括祖先节点)仅更新一次。 1 对 1 的情况:(k log n) 搜索每个节点,然后另一个 (k * avg depth) 用于更新祖先(一些重复)。在问题示例中,这不会显着减少吗?
    • 当然,您的新二叉树示例本质上就是我所说的遍历整个结构,因为通常它会在树的两侧进行递归调用。当然,在特殊情况下,您也许可以提前停止并避免遍历树的某些部分。在其他情况下,您可能仍然需要遍历树的一部分,才发现树的那部分实际上没有发生任何变化。如果您想避免不必要的分配,那么我提到的额外布尔返回值可能会很有用。
    • 只有当有要更新的节点与该分支匹配时,您才会沿着该分支向下走。它的搜索方式与 1 by 1 的情况完全相同,但它同时进行所有 k 次搜索。所以你永远不需要返回一个关于它是否更新的布尔值,因为除非它被更新,否则你不会从那个分支开始。
    • 如果您可以通过查看特定节点来判断您无需在该节点或其任何后代处执行任何操作,那么您根本不必进入该节点.然而,通常你可以告诉你不需要在节点本身做任何事情,但不一定知道后代,在这种情况下,你可能无论如何都需要遍历后代。
    • 鉴于我们讨论的是与 O(k log n) 情况的比较,我假设我们讨论的是相同类型的操作,这意味着我们将确切地知道如何搜索以及何时搜索停止。您不了解后代的情况通常需要详尽搜索,因此与我的问题无关。在这个批量更新问题中,我们有一个现有节点的列表,我们想要删除它们,或者修改其中的字段,或者以某种方式用更新的值替换它们。
    【解决方案2】:

    您正在寻找的特定实现可以在 Clojure(和 ClojureScript)的transients 中找到。

    简而言之,给定一个完全不可变的持久性数据结构,它的瞬态版本将使用破坏性(分配效率高)突变进行更改,完成后您可以再次将其翻转回适当的持久性数据结构与您对性能敏感的操作。只有在转换回持久数据结构时,才会创建新的根(例如),从而将伴随的成本摊销在结构处于临时形式时对结构执行的逻辑操作的数量上。

    【讨论】:

      猜你喜欢
      • 2011-07-01
      • 1970-01-01
      • 1970-01-01
      • 2013-04-17
      • 1970-01-01
      • 1970-01-01
      • 2011-03-07
      • 1970-01-01
      相关资源
      最近更新 更多