修剪数组中大值的数据结构?

时间:2012-01-23 17:23:34

标签: arrays algorithm data-structures

我需要有一个数据结构用于以下目的。假设我有一个数组a。最初所有元素都设置为零。每当我要更新位置new_value上具有正值p的元素之一时,如果位置p old_value的原始值非零并且大于new_value,然后我需要更新从位置p一直到数组末尾的所有非零元素。此处更新意味着重置值与该位置的旧值和new_value之间的较小值。

例如,数组是: [2, 0, 3, 0, 2, 1, 5, 0, 4, 0, 7]

给定位2的新值4(从0开始)具有旧值3,我需要将数组更新为:

[2, 0, 3, 0, 2, 1, 4, 0, 4, 0, 4]

如果位置2的新值为1,则结果数组为:

[2, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1]

是否有已知的数据结构可以有效地执行此操作?我需要进行大量此类更新操作。

感谢您的建议。

3 个答案:

答案 0 :(得分:1)

我相信通过使用splay树的修改,您可以通过每个元素访问或值更改的分摊O(log n)时间来实现此工作。这种方法背后的想法是双重的。首先,我们不是将数组存储为数组,而是将其存储为一对包含原始值的数组,以及一个splay树,其中每个节点的键是数组的索引。例如,给定一个包含七个元素的数组,设置可能如下所示:

Array: 3 1 4 2 5 9 3
Tree:             3
                /   \
               1.    5
              / \.  / \
             0.  2 4.  6

请注意,树将索引保存到数组中而不是数组元素本身。如果我们想要在特定索引处查找值,我们只需对索引执行splay树查找,然后在给定位置返回数组元素,该元素占用O(log n)时间。

您希望支持更改所有未来值的操作我将称之为“上限”操作,因为它会在当前值之后设置所有值的上限。考虑这个问题的一种方法是数组中的每个元素都有一个与之相关的上限(最初是无穷大),而元素的真值则是其真实值和上限的最小值。诀窍在于,通过使用展开树,我们可以在摊销的O(log n)时间内调整所有值的上限或超过某一点。为此,我们通过让每个节点存储一个值c来表示该元素向前强加的上限,然后根据需要对c进行适当的更改来扩充splay树。

例如,假设我们想要从某个元素向前强加上限。我们假设这个元素已经在树的根部。在这种情况下,我们只需将其c值设置为O(1)时间内的新上限。从那时起,每当我们查找在该元素之后或之后出现的某个值时,我们就可以记下天花板,因为我们沿着树从根向下行走到相关元素。更一般地说,当我们进行查找时,每次我们遵循正确的子链接时,我们都会注意到该节点的c值。一旦我们遇到了有问题的元素,我们就知道元素的上限,因为我们可以从我们所遵循的正确孩子的根目录中获取路径上节点的最小上限。因此,为了在结构中查找元素,我们进行标准的splay树查找,跟踪c值,然后输出origin数组值和c值的最小值。

但是为了使这项工作,我们的方法必须考虑到展开树进行旋转的事实。换句话说,我们必须展示如何在旋转期间传播c值。假设我们想要像这样进行轮换:

    A.         B
   /.      ->.  \
  B.             A

在这种情况下,我们不会更改任何c值,因为在A之后查找的任何值仍将通过A节点。但是,如果我们进行相反的旋转并将A拉到B以上,那么我们将A的c值设置为B的c值和A的c值的最小值,因为如果我们在执行旋转后下降到A的左子树,我们需要因子B的上限考虑在内。这意味着我们每次轮换执行O(1)工作,并且由于每个splay执行的分摊转数是O(log n),因此我们按每次查找分摊O(log n)工作。

要完成图片,要更新任意上限,我们将天花板要更改的节点展开到根,然后设置其c值。

简而言之,我们有O(log n)查找O(log n)更改时间(摊销)。

这个想法是基于对Sleator和Tarjan的原始论文“Self-Adjusting Binary Search Trees”中的链接/剪切树的讨论,该文章还介绍了splay树。

希望这有帮助!

答案 1 :(得分:1)

我最初的想法有点类似于templatetypedef's answer(顺便说一句,+ 1),但使用简单的静态二叉树而不是展开树。与该答案一样,“逻辑”数组L由包含原始值的实际数组A和强加的上限值的二叉树T表示。 (在这种情况下,天花板值树的 shape 是静态的,因此我们不需要跟踪树中元素的索引:对应于节点的索引就是它的简单有序遍历索引。)树可以是任何形状,只要它具有最小高度;也就是说,如果L要包含n元素和2^(k-1) <= n < 2^k,则树应该具有高度k。我建议一个布局,我们将元素2^(k-1) - 1放在树的根部,使其左子树成为包含L[0 .. 2^(k-1) - 2]的{​​{3}}树,并为L[2^(k-1) .. n - 1]递归地定义其右子树(也就是说,它可能是空的)。例如,12元素树的条目如下:

              7
       /-----/ \-----\
      3               11 
   /-/ \-\         /-/
  1       5       9   
 / \     / \     / \
0   2   4   6   8   10

(请注意,这些数字不是树的条目 - 它们只是指示树的条目对应于数组中的哪个位置。)此描述还给出了在树中找到与条目对应的条目的算法i中的n:如果i < 2^(k-1) - 1然后在完美的左子树中找到i条目,如果i = 2^(k-1) - 1则它是根,然后找到<{1}}以递归方式在右子树中输入i - (2^(k-1) - 1)

我们将所有树条目初始化为无穷大。当我们要将n - (2^(k-1) - 1)的上限强加到条目c之后,我们会在树中找到i条目,如下所示:

  • 如果我们正在查看的节点i是第x个节点,或者我们需要进入其 left 子树,我们会更新存储在节点{{ {1}}至i的最小值和x的当前值。
  • 如果我们需要进入c子树,并且x中存储的当前值最多为x,我们就不需要了更新树 - 它已经执行,所以我们可以停止。
  • 如果xc节点,我们可以停止。

这就是强加上限。请注意,x本身永远不会更新。

如果我们要查找i的{​​{1}}值,那么我们将局部变量A初始化为无穷大,并在树中找到i条目根据这些规则:

  • 如果我们正在查看的节点L是第c个节点,或者我们需要进入其子树,请将i设置为其当前值的最小值和x处存储的值。
  • 如果ic节点,我们可以停止。

现在我们返回xx的最小值。

这两项操作均为i(每项操作的实际时间,未摊销)。要实现,请注意您可以使用数组来保存二叉树。

答案 2 :(得分:0)

我相信Cartesian tree可以帮助您解决问题。与维基百科中描述的唯一区别在于我建议你在每个点头中构建最大堆而不是最小堆(即将属性更改为每个节点的值不小于它的子节点)。 您需要转到当前节点的右子节点,然后沿着树向下移动,更改所有元素,其值大于新值。您可以通过这种方式证明您不会检查3 * K个元素,其中K是需要更改的元素的实际数量。另一个好处是,通过执行操作,您将描述每个顶点中的堆属性仍将保留,因为您将 all 更改为大于新值的值。

希望这个答案有所帮助。