Persistent Data Structures(持久化数据结构)
1概述
在本篇中,我们介绍持久化数据结构,持久化数据结构就是我们保存过去状态的所有信息的数据结构。他是更大的类别时态数据结构的一部分。另一种时态数据结构,回溯数据结构,我们在第二篇展示。
通常,我们通过改变现有数据结构中的某些东西来处理数据结构的更新:要么是它的数据,要么是组织它的指针。在这个过程中,我们丢失了先前数据结构状态的信息。持久数据结构则不会丢失任何信息。
对于一些数据结构和持久性定义的情况,可以用渐进最小的额外工作或空间开销将普通数据结构转换为持久性结构。
在这一领域反复出现的一个主题是,模型对结果至关重要。
部分持久性和完全持久性对应于一个分支宇宙模型的时间旅行,例如《终结者》和《似曾相识》的第一部和第二部。
2模型和定义
2.1数据结构中的指针机模型#
在这个模型中,我们认为数据结构是具有数据项的有界节点的集合。节点中的每一段数据既可以是实际数据,也可以是指向某个节点的指针。
该模型允许的基本类型操作如下:
1.
2.
3.
4.
5.
通过这些形状约束和操作实现的数据结构包括链表和二叉查找树,通常与C语言中的struct或Java中的对象相对应。不属于这一组的数据结构的一个例子是大小可变的结构,如数组。
2.2持久性定义#
我们已经模糊地将持久性称为回答关于结构过去状态的查询的能力。这里我们给出了几个关于持久性的定义。
1.部分持久性---在这个持久化模型中,我们可以查询任何以前版本的数据结构,但我们只更新数据结构的最新版本。我们有两个操作
2.完全持久化---在这个模型中,数据结构的任何版本都允许更新和查询。我们有两个操作
3.融合持久化---在这个模型中,除了先前的操作之外,我们允许组合操作将多个先前版本的输入组合起来,以输出一个新的单一版本。我们有三个操作
4.函数持久化---这个模型得名于函数式编程,其中对象是不可变的。这个模型中的节点同样是不可变的:修改不会改变数据结构中的现有节点,而是创建新的节点。Okasaki在他的书中讨论了这些以及其他的函数式数据结构。
函数持久化和其他持久化的区别在于,我们必须保持所有与之前版本相关的结构完整:唯一允许的内部操作是添加新节点。在前三种情况下,只要能够实现接口,我们就可以做任何事情。
上述4个持久化后面的持久化要比前面的更强。函数式包含着融合,融合包含着完全,完全包含着部分。
函数式包含着融合,因为我们只是限制了实现持久化的方式。如果我们限制自己不使用组合器,融合持久化就会变成完全持久化。当我们限制自己只写入最新版本时,完全持久化就变成部分持久化。
3部分持久化
问题:能否高效地实现部分持久化?
答案:是的,假设指针机内存模型和数据节点的入度限制为
证明的思路:我们将扩展我们的数据节点以保存修改,也就是mods log
。当我们修改了一个节点足够多的时候,我们创建一个新的节点来接受进一步的更新,直到它也被填满。
对于旧数据结构中的每个节点,新数据结构有一个节点的集合:一个是最新版本的当前节点,可能有许多旧节点只用于读取旧版本。每次我们archive
一个节点时,我们也会更新所有的(版本化的)指针,以指向最新的节点。
3.1证明#
扩展数据节点,包含以下信息:
1.用于数据和指针的只读区域(对应于原始结构中的数据和指针)。
2.(new)反向指针的可写区域。如果节点
3.(new)一个可写修改(mods
)区域,用于存储表单中的条目(字段、版本、值)。内存区的大小也需要固定,这对写性能也有重要影响。
我们实现读写操作如下:
1.
2.
- 将每个字段(数据和向前指针)的最新版本复制到static field部分。
- 也复制指向
的反向指针 - 对于每个节点
,使 指向 ,将其后向指针重定向到 (使用我们的指针获取它们)(最多 个)。 - 对于使
指向 的每个节点 ,递归调用 (最多递归调用 次)。
对于一些数据结构,如列表或树,我们通常提前知道
3.2分析#
- 空间:
如果我们选择mod log的边界大小为
请注意,根节点有一个特殊情况——为了根节点不溢出,请确保每个版本都指向根节点的适当版本。
- 时间:
读取是廉价的,它需要恒定的时间来检查单个节点的mod log并选择所需的版本。如果mod log中有空间,写入操作也很便宜。否则,一次写入操作的开销会很大。让
其中
显然,由于在递归步骤中可能会拆分许多数据节点,因此成本可能会很大。但是,我们知道当一个节点变得满时,那么下一次操作可能会找到一个更空的mod日志。因此,平摊分析比最坏情况分析更适合这种操作。
回想一下势函数法:如果我们知道一个势函数φ,那么平摊成本(n) =成本(n) +∆φ。
考虑以下势函数:
由于节点已满,现在为空,因此与新节点相关的势能变化为
对节点
通过将递归展开一次,我们可以看到,在每次展开时,
Brodal的进一步研究表明,在最坏情况下的实际开销也是O(1)。
4完全持久化
版本表示问题的解决方法是用一棵树来表示版本结构,以及用线性化的方式有效地表示这棵树。
树的线性表示是开始节点a
,而结束节点a
。这种表示对树进行无损编码,我们可以使用这种编码表示直接回答有关树的查询。例如,我们知道
这种线性化的表示可以使用order maintenance
数据结构来实现。现在,只要顺序维护数据结构支持以下两种操作就足够了,而且时间复杂度都是O(1)。
- 在指定元素之前或之后插入一项。
- 检查项
是否在项 之前。
例如,链表支持
为了实现版本树查询,例如版本v是否是版本w的祖先
,我们可以使用两个比较查询将版本v添加为版本w的子版本
这样的更新,我们可以在
4.2模型构造与算法#
数据结构中的节点将保留与部分持久情况相同类型的额外数据。
对于每个节点,我们存储
1.
2.
-
将节点 的mod log分成两部分。这里需要划分为子树,而不是任意划分。现在,节点 拥有内部树的一些mod,而节点 保留了“旧的”一半更新。 -
从节点
的“旧的”mod条目中,计算每个字段的最新值,并将它们写入节点 的数据和反向指针部分。 -
递归更新所有d + p + (d + p + 1)邻居的前向和反向指针。
-
将新版本插入到我们的版本树表示中。
4.3分析#
-
空间---如果我们不拆分或
当我们拆分一个节点时,两者都是 。 -
时间---
的实现方式与部分持久化类似。我们使用辅助版本树数据结构,在时间复杂度为O(1)的列表中,从O(1)的元素中找出version的最大祖先。
与部分持久化一样,当节点的mods log中有空间时,写操作的成本很低,而当节点已满时,写操作的成本更高。
考虑
OPEN Question:完全持久性的摊销
OPEN Question:完全持久性和部分持久性是否都有一个匹配的下界?
5融合持久化
融合持久性提出了新的挑战。首先,我们需要找到版本的新表示。我们的树遍历技术没有扩展到DAGs。而且,这是可能的在
串联的Deques(允许堆栈和队列操作的双端队列)可以在固定的时间内完成每次操作(Kaplan, Okasaki和Tarjan)。与字符串一样,我们可以通过递归地将deque与其自身连接起来,在多项式时间内创建隐式指数deque。
由Fiat和Kaplan的一般性转换如下:
-
这一措施被称为版本DAG的“有效深度”:如果我们通过DFS(即通过复制每个路径而不重叠)去分解树并重新平衡树,这便是我们所希望达到的最佳效果。 -
-
上界:
这个结果反映了
下限也由Fait和Kaplan提出
OPEN Question:每个操作的空间开销为
Collette、Iacono和Langerman考虑了“不相交操作”的特殊情况:只在没有共享数据节点的版本之间执行合并操作。在这种情况下,
如果我们只允许不相交操作,那么每个data节点的版本历史记录就是一棵树。当计算read(node, field, version)时,树的情况:当node在version处被修改时,我们只是读取新版本。在节点沿着路径进行修改之间,我们首先需要找到最后的修改。这个问题可以通过使用“链接切割树”来解决(参见第19讲)。最后,当版本位于叶子节点之下时,问题就更加复杂了。证明使用了如分数级联等技术。
6函数持久化
以下是现有技术的简单示例。
-
功能均衡的BST---为了在功能上持久化BST,其主要思想(又称“路径复制”)是复制被修改的节点,并通过复制所有祖先节点来传播指针更改。如果没有父指针,则自顶向下工作。假设树是平衡的,这种技术每次操作的开销是
。Demaine, Langerman, Price展示了这一点。 -
Deque---(允许栈和队列操作的双端队列),每次操作(Kaplan、Okasaki和Tarjan)的连接操作都可以在常量时间内完成。此外,Brodal, Makris和ticchlas得到通过拼接可以在常数时间内完成,更新和搜索的时间为
。 -
Tries---local navigation,子树复制和删除。Demaine, Langerman, Price展示了如何在最优地保持这种结构。Functional Tries就是融合数据结构的一个例子:可以从旧版本中复制一个子树并插入到新版本中,从而将两个版本合并在一起。
Pippenger得出函数持久化与常规数据结构之间最多
OPEN Question:(对于函数和融合)更大的分割?更一般的结构转换?
OPEN Question:列表拆分和连接?一般指针机器?
SOLVED:Blame Trees
OPEN Question:使用剪切和粘贴的数组?特别DAGs?
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律