用于反转 log(n) 中的子数组的数据结构

Data structure for inverting a subarray in log(n)

构建具有函数的数据结构:

set(arr,n) - 用长度为 n 的数组 arr 初始化结构。时间 O(n)

fetch(i) - 获取 arr[i]。时间 O(log(n))

invert(k,j) -(当 0 <= k <= j <= n 时)反转子数组 [k,j]。意思是 [4,7,2,8,5,4]invert(2,5) 变成 [4,7,4,5,8,2]。时间 O(log(n))

如何将索引保存在二叉搜索树中并使用一个表明索引已反转的标志?但是如果我做超过 1 次反转,它就会搞砸。

下面是我们如何设计这样的数据结构。 事实上,使用平衡二叉搜索树是一个很好的开始。

首先,让我们将数组元素成对存储 (index, value)。 自然地,元素按索引排序,因此树的中序遍历将以其原始顺序生成数组。

现在,如果我们维护一个平衡的二叉搜索树,并在每个节点中存储子树的大小,我们已经可以在 O(log n) 中执行 fetch

接下来,让我们只假装我们存储索引。 相反,我们仍然像处理 (index, value) 对一样排列元素,但只存储值。 index 现在被 隐式存储 并且可以按如下方式计算。 从根开始,向下到目标节点。 每当我们移动到左子树时,index 都不会改变。 移动到右子树时,将左子树的大小加一(当前顶点的大小)到 index.

此时我们得到的是一个定长数组,存储在平衡二叉搜索树中。访问(读取或写入)任何元素都需要 O(log n),而不是普通固定长度数组的 O(1),因此是时候从所有麻烦中获得一些好处了。

下一步是设计一种方法,在给定左侧部分所需大小的情况下,将我们的数组拆分为O(log n)中的左右部分,合并 两个数组串联。 这一步引入了对我们选择的平衡二叉搜索树的依赖。 Treap 是明显的候选者,因为它建立在 splitmerge 基元之上,因此此改进是免费的。 也许也可以在 O(log n) 中拆分一个 Red-black tree or a Splay tree(尽管我承认我自己并没有试图弄清楚细节)。

现在,该结构已经比数组更强大:它允许在 O(log n) 中拆分和连接 "arrays",尽管元素访问也和 O(log n) 一样慢。 请注意,如果我们此时仍显式存储 index,这是不可能的,因为索引会在 splitmerge 的右侧被破坏操作。

终于到了介绍invert操作的时候了。 让我们在每个节点中存储一个标志,以指示是否必须反转该节点的整个子树。 这个标志将延迟传播:每当我们访问一个节点,在做任何事情之前,检查标志是否是true。 如果是这种情况,交换左右子树,toggle(true <-> false)两个子树的根节点中的flag,将当前节点中的flag设置为false.

现在,当我们想要反转子数组时:

  • 通过两次split操作将数组分成三部分(子数组之前、子数组本身和子数组之后),
  • toggle (true <-> false) 中间(子数组)部分根部的flag,
  • 然后通过两次 merge 操作将这三个部分合并回原来的顺序。