跳过列表(Pugh,1990)为排序字典提供了对数时间操作,如搜索树,但跳过列表更适合并发更新。
是否可以创建一个高效的纯功能并发跳过列表?如果没有,是否有可能创建任何一种高效的纯功能并发排序字典?
跳过列表(Pugh,1990)为排序字典提供了对数时间操作,如搜索树,但跳过列表更适合并发更新。
是否可以创建一个高效的纯功能并发跳过列表?如果没有,是否有可能创建任何一种高效的纯功能并发排序字典?
跳过列表的特性使它们有利于并发更新(即大多数加法和减法都是局部的)也使它们不利于不变性(即列表中的许多早期项目最终指向后面的项目,并且必须被改变)。
具体来说,跳过列表由如下结构组成:
NODE1 ---------------------> NODE2 ---------...
| |
V V
NODE1a --> NODE1b ---------> NODE2a --> NODE2b --> NODE2c --- ...
现在,如果您有一个更新,例如删除NODE2b
or NODE1b
,您可以在本地处理它:您只需分别指向2a
or2c
就1a
可以2a
了。不幸的是,因为叶子节点都指向另一个,所以对于功能性(不可变)更新来说,这不是一个好的结构。
因此,树结构更适合不变性(因为损坏总是局部受限的——只是您关心的节点及其直接父节点通过树的根部)。
并发更新不适用于不可变数据结构。如果您考虑一下,任何功能解决方案都有A
as的更新f(A)
。如果您想要两个更新,一个由给出,一个f
由非常聪明的东西)。g
f(g(A))
g(f(A))
h = f,g
但是,并发读取在不可变数据结构中工作得非常好,因为您可以保证没有状态更改。如果您不假设您可以在任何其他写入中断之前解决读/写循环,那么您永远不必锁定读取。
因此,写入繁重的数据结构可能更好地实现可变(并且使用类似跳过列表的东西,您只需要在本地锁定),而读取繁重的数据结构可能更好地实现不可变(树是更自然的数据结构)。
Andrew McKinlay 的解决方案是这里真正的跳过列表的真正“真正”功能解决方案,但它有一个缺点。您花费对数时间来访问一个元素,但现在超出头部元素的突变变得毫无希望。你想要的答案被埋没在无数的路径副本中!
我们能做得更好吗?
部分问题在于从 -infinity 到您的项目有多个路径。
但是,如果您仔细考虑搜索跳过列表的算法,则永远不会使用该事实。
我们可以将树中的每个节点视为具有首选链接,即从左侧到它的最顶部链接,在某种意义上可以将其视为“拥有”该条目。
现在我们可以将“手指”的概念考虑到数据结构中,这是一种功能性技术,使您能够专注于一个特定元素,并提供返回根的路径。
现在我们可以从一个简单的跳过列表开始
-inf-------------------> 16
-inf ------> 8 --------> 16
-inf -> 4 -> 8 -> 12 --> 16
按级别展开:
-inf-------------------> 16
| |
v v
-inf ------> 8 --------> 16
| | |
v v v
-inf -> 4 -> 8 -> 12 --> 16
去掉除首选指针之外的所有指针:
-inf-------------------> 16
| |
v v
-inf ------> 8 16
| | |
v v v
-inf -> 4 8 -> 12 16
然后,您可以通过跟踪您必须翻转才能到达那里的所有指针,将“手指”移动到位置 8。
-inf ------------------> 16
^ |
| v
-inf <------ 8 16
| | |
v v v
-inf -> 4 8 -> 12 16
从那里可以删除 8,将手指推到其他地方,然后您可以继续用手指在结构中导航。
这样看,我们可以看到跳过列表中的特权路径形成了一个生成树!
如果您在树中只有特权指针并使用像这样的“瘦节点”,则用手指移动 1 步是 O(1) 操作。如果您使用胖节点,那么手指向左/向右移动可能会更昂贵。
所有操作都保持为 O(log n),您可以像往常一样使用随机跳过列表结构或确定性结构。
也就是说,当我们将跳过列表分解为首选路径的概念时,我们会得到跳过列表只是一棵树,其中包含一些我们不需要插入/搜索/删除的冗余非首选链接,因此从右上角开始,每条路径的长度为 O(log n) ,概率很高或有保证,具体取决于您的更新策略。
即使没有手指,您也可以使用这种形式在树中每次插入/删除/更新保持 O(log n) 预期时间。
现在,您的问题中没有意义的关键词是“并发”。纯粹的函数式数据结构没有就地突变的概念。你总是会产生新的东西。从某种意义上说,并发功能更新很容易。每个人都有自己的答案!他们只是看不到对方'。
不是跳过列表,但似乎与问题描述相匹配:Clojure 的持久性红黑树(请参阅PersistentTreeMap.java)。来源包含此通知:
/**
* Persistent Red Black Tree
* Note that instances of this class are constant values
* i.e. add/remove etc return new values
* <p/>
* See Okasaki, Kahrs, Larsen et al
*/
这些树维护元素的顺序,并且在 Rich Hickey 使用这个词的意义上是“持久的”(不可变的并且能够在构建更新版本时保持其性能保证)。
如果您想使用它们,可以使用函数在 Clojure 代码中构造实例sorted-map
。
如果您只需要在跳过列表的前面进行 cons,那么应该可以制作一个持久不可变的版本。
这种跳过列表的优点是“随机”访问。例如,您可以比在常规单链表中更快地访问第 n 个元素。