可持久化数据结构

时间:2021-01-19 10:42:35

  我们经常会遇到这样的问题:我们需要维护一个数据结构,我们可以修改单一结点的值,查询单一结点的值,但是最关键的是我们可能还需要回退之前做过的某些操作。这里回退是指回到未做这些操作之前的状态。

  在无回退操作的情况下,我们有大把的数据结构可供选择来解决这些问题。但是一旦涉及到回退操作,选择就少的多了。我们将支持回退操作的数据结构称为可持久化数据结构。

  稍微思考一下如何可以在原来数据结构的基础上使其变得可持久化,有一个很简单的方案。我们每次操作都将重新建立一个新的数据结构,并将之前的操作都先在其上执行一次,之后执行该次操作。我们按操作执行顺序将这些数据结构维护成一个序列S,此时S[0]表示未经任何操作的初始数据结构。对于i>0,S[i]表示在S[0]的基础上执行过序号1到i的所有操作后得到的新的数据结构。在这样的做法下,我们称S[i]为版本i,回退操作等价于切换到某个特定版本。若操作i表示切换为版本j,那么我们可以直接将S[i]设置为S[j]的克隆。

  上面提到的做法下很容易发现可以使得任意数据结构都可以支持回退操作,但是缺点也是非常明显,空间和时间的复杂度都奇高。每一次操作都需要累加之前操作的时间复杂度,空间也是,我们为了保存各个版本需要耗费大量的内存。

  先说明时间复杂度的优化,对于i号操作,我们完全可以直接克隆版本S[i-1]并在其上执行i号操作,这样时间复杂度基本上就向空间复杂度看齐了。下面我们就可以专注于空间复杂度的优化(对应的也就是时间复杂度的优化)。

  数据结构是用于保存数据的,我们将其保存数据的单元称为结点,我们可以利用结点来刻画整个数据结构的骨架。数据结构基本分为两类,一类是稳定的,一类是不稳定的。稳定的数据结构,其特定是在修改的结点的值之后不会改变结点之间的关系,而不稳定的数据结构在结点值变更后需要重新维护结点之间的关联。稳定的数据结构有线段树,后缀数组,前缀树等等,不稳定的数据结构主要就是各种二叉平衡树。对于稳定的树状结构,若孩子没有保存指向父结点的指针,即由父亲负责记录所有的孩子,我们很容易发现,当我们对某个结点更改时(修改值,新增,删除等操作),我们只需要同时修改该结点的所有祖先结点即可,那我们是不是也可以只克隆这些结点而非整个数据结构呢?答案是肯定的。由于父亲维护孩子,因此一个孩子允许有多个父亲,故所有没有被直接影响的结点都可以继续复用。我们将部分树状数据结构(特定是稳定和父亲维护父子关系)的一次操作的空间复杂度优化到了O(h),其中h是树状数据结构的高度。

  当我们将上面的想法作用到线段树时,就得到了常说的主席树。其高度为O(log2(n)),其中n为线段树维护的区间大小,同时其时间和空间复杂度均为O(log2(n))。