持久数据结构的高效批量修改

时间:2011-05-26 17:16:09

标签: data-structures immutability

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

但是,如果我有一个10,000个节点的树,我需要修改1000个节点呢?我不想通过创建1000个新根,我只需要一次修改所有内容所产生的新根。

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

在批量更新案例中我们可以这样做: 您将在一次传递中更新1000个节点,而不仅仅是更新单个节点。

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

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

2 个答案:

答案 0 :(得分:8)

这种“批量修改”操作有时称为批量更新。当然,细节将根据您正在使用的数据结构类型以及您尝试执行的修改类型而有所不同。

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

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

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

答案 1 :(得分:3)

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

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