在固定的固定时间内初始化数组-这个技巧叫什么?


13

有这种数据结构可以交换数组访问的性能,而不需要在清除数组时对其进行迭代。您需要为每个条目保留一个世代计数器,以及一个全局世代计数器。“清除”操作会增加生成计数器。在每次访问时,您都要比较本地生成计数器和全局生成计数器。如果它们不同,则将该值视为“干净”。

这是最近在Stack Overflow上的答案中提到的,但是我不记得这个技巧是否有正式名称。可以?

如果只需要放松节点的一小部分子集,并且必须重复进行一次,Dijkstra算法就是一个用例。


2
有趣的把戏,但它有相当大的开销。因此,我想知道哪个用途将清除数组作为价格支付的常见操作?(真诚的问题!)
约阿希姆·绍尔

@JoachimSauer:编辑。
krlmlr 2012年

在一般情况下,对于内存使用和访问成本而言,这听起来都非常昂贵。此技术的用例必须非常具体。
马丁·约克

3
@Joachim:它用于快速清除缓冲区以进行渲染。它们只是每64kb或类似的东西有一个“清除位”。
DeadMG '07年

3
@ user946850“摊销”意味着您可以证明昂贵的操作在整体情况下很少发生,并且其贡献不超过例如O(1)

Answers:


2

前述方法要求每个单元能够容纳足够大的数目以容纳可能需要重新初始化阵列的次数,这是相当大的空间损失。如果一个时隙是能够保持其中将永远不会被合法地写入的至少一个值,可避免任何其他(非恒定)的空间在加入为代价惩罚O(Wlg(N))时间损失,其中W是的数目不同之间写入阵列的槽清除操作,N是数组的大小。例如,假设一个将存储从-2,147,483,647到2,147,483,647(但从不包括-2,147,483,648)的整数,并且一个人希望将空白数组项读取为零。首先用-2,147,483,648填充数组(调用该值B)。读取应用程序的阵列插槽时,报告值B为零。书写阵列时隙之前I,检查它是否保持B并且如果是这样并且I是大于一,一个零存储槽I/4用于该位置进行类似的检查之后(并且,如果它保持BI/16等等)。

要清除数组,请从I0或1开始,具体取决于数组的基数(所描述的算法适用于任何一种)。然后重复以下过程:如果item IB,则递增,I并且如果这样得出四的倍数,则将其除以4(如果除以得出的值为1,则终止)。如果item I不是B,则存储B在那里并乘以I四(如果I从零开始,乘以四将使其保持零,但是由于item 0将为空白,因此I将递增)。

注意,可以用其他数字代替上面的常数“ 4”,较大的值通常需要较少的工作标签,而较小的值通常需要较少的工作清除;由于必须清除带有标签的阵列插槽,因此几乎可以肯定,最佳值为3或4。由于值四肯定接近最佳值,比二或八好,并且比其他任何数更方便,所以这似乎是最合理的选择。


在所有单元格都用新值更新之前,拥有一个能够容纳足够的顺序复位的版本计数器就足够了。实际上,在更紧密的循环中,一个字节可能就足够了,甚至更少。
9000 2012年

@ 9000:依赖于这种行为的代码容易变得脆弱,尤其是考虑到使用这种“伪清除”方法(而不是简单地清除数组)的唯一原因是,如果需要要清除的物品通常很小且变化多端,这是一对条件,它们共同提高了一件物品可能被使用,“清除”并随后长时间保持不变的可能性。您可以考虑在计数器要换行时扫描阵列并物理清除所有旧插槽,但是...
supercat

1
...如果计数器的换位值是恒定的,则每个数组清除操作的平均工作量为O(N),其中N为数组的大小。并不是说这样的事情在实践中可能没有用,因为以65,536倍加速的O(N)实现仍将是O(N),但其速度也将是未经改进的O(N)的65,536倍。顺便说一句,使用稀疏数组数据结构也可能会受益于这些方法,该稀疏数组数据结构可以使用O(AlgN)空间来保存大小为N且数组为A的非空白元素的数组。
超级猫


1

我相信这是一种记忆的特殊情况,但在这种情况下,全局计数器的每个增量都会隐含“年龄”。我猜是一种“向后记忆”。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.