间隔图的数据结构


11

n为整数,令Z表示所有整数的集合。令[a,b]表示整数的间隔{a,a+1,a+2,,b}

我正在寻找一种数据结构来表示映射f:[1,n]Z。我希望数据结构支持以下操作:

  • get(i)应该返回f(i)

  • set([a,b],y)应更新f使得f(a)=f(a+1)==f(b)=y,即更新f到一个新的地图f使得f(i)=y用于i[a,b]并且f(i)=f(i)i[a,b]

  • 应该返回最大间隔 [ b ],使得[ b ]并且 ˚F是常数 [ b ](即, ˚F = ˚F 一个+ 1 = = ˚F b )。stab(i)[a,b]i[a,b]f[a,b]f(a)=f(a+1)==f(b)

  • 应更新 ˚F到一个新的地图 ˚F '使得 ˚F '= ˚F + δ[ b ]并且 ˚F '= ˚F [ a b ]add([a,b],δ)fff(i)=f(i)+δi[a,b]f(i)=f(i)i[a,b]

我希望每个操作都高效。我认为O(1)O(lgn)时间是有效的,但是O(n)时间太慢。如果运行时间是摊销的运行时间,则可以。是否有一种数据结构可以同时提高所有这些操作的效率?

(我注意到在一些编程挑战中也出现了类似的模式。这是一个概括,足以解决所有这些挑战问题。)


我猜八卦树是起点。虽然[ a b ]add的子间隔数是线性的;您是否考虑过懒散压缩带有一元“ + δ ”节点的八叉树?[a,b]+δ
吉尔斯(Gilles)'所以

考虑使得所有ij的f i f j 。然后,您必须将n个值存储在某个位置。执行set [ a b ] y 必须以某种方式摆脱这些值(通过重写或丢弃它们-您可以使用GC推迟执行,但有时必须执行O n 操作) 。这样,该运算将为O n ff(i)f(j)ijnset([a,b],y)O(n)O(n)
avakar 2013年

@avakar,我对将GC视为有效的“免费”解决方案感到满意。更一般而言,我对解决方案感到满意,该解决方案将运行时间摊销为运行时间(因此,可以将GC的成本首先摊在创造价值的成本中)。
DW

您注意到常数时间和对数时间有效,线性时间慢。将时间是否太慢,无法满足您的需求?O(nlgn)
jbapple

@jbapple,嘿,这是一个开始!我认为值得记录作为答案。
DW

Answers:


4

我相信所有查询的对数时间都是可以实现的。主要思想是使用间隔树,其中树中的每个节点对应于索引间隔。我将从一个简单的数据结构版本开始(它可以支持get和set,但不支持其他操作)来构建关键思想,然后添加功能以支持其他功能。

一个简单的方案(支持获取和设置,但不支持添加或刺穿)

假设如果函数f[ a b ]上恒定,即,如果f a = f a + 1 = = f b ,则间隔平坦的[a,b]f[a,b]f(a)=f(a+1)==f(b)

我们的简单数据结构将是一个间隔树。换句话说,我们有一个二叉树,其中每个节点对应一个(索引的)间隔。我们将在树的每个节点v中存储相应的间隔。每片叶子将对应一个平坦的间隔,并且它们的排列方式将使得从左到右的读取叶子给我们提供了一系列连续的平坦间隔,这些间隔不相交并且其并集为[ 1 n ]。内部节点的间隔将为其两个子节点的间隔的并集。另外,在每个叶节点我们将存储值V I(v)v[1,n]V()的函数在区间对应于该节点(请注意,此间隔是平的,所以˚F是在间隔恒定,所以我们只储存的单个值˚F在每个叶节点)。fI()ff

同样,您可以想象我们将划分为平坦的间隔,然后数据结构是一个二进制搜索树,其中的键是这些间隔的左端点。叶子含有的值˚F在一些索引范围,其中˚F是恒定的。[1,n]ff

使用标准方法来确保二叉树保持平衡,即其深度为(其中m表示树中当前的叶子数)。当然,Ñ,所以深度总是至多Ô LG Ñ 。下面将对您有所帮助。O(lgm)mmnO(lgn)

现在,我们可以支持以下get和set操作:

  • 很简单:我们遍历树以查找其间隔包含 i的叶子。这基本上只是遍历二叉搜索树。由于深度是 ø LG Ñ ,运行时间是 ø LG Ñ get(i)iO(lgn)O(lgn)

  • 比较棘手。它是这样的:set([a,b],y)

    1. 首先,我们找到包含a的叶间隔。如果a 0 < a,则我们将该叶子间隔分为[ a 0a - 1 ][ a b 0 ]两个间隔(因此将此叶子节点转换为内部节点并引入两个子节点)。[a0,b0]aa0<a[a0,a1][a,b0]

    2. [a1,b1]bb<b1[a1,b][b+1,b1]

    3. [a,b]O(lgn)O(lgn)y

    4. 最后,由于我们修改了树的形状,因此将执行任何必要的旋转以重新平衡树(使用任何标准技术来保持树的平衡)。

    O(lgn)O(lgn)O(lgn)

O(lgn)O(lgmin(n,s))s

添加对添加的支持

我们可以修改上面的数据结构,以便它也可以支持添加操作。特别是,不是将函数的值存储在叶子中,而是将其表示为存储在一组节点中的数字的总和。

更准确地说,输入处的函数的值将作为树的根向下到其间隔包含的叶子的路径上的节点中存储的值的总和而可恢复。在每个节点我们将存储一个值;如果表示叶的祖先(包括叶本身),则函数在值将是。f(i)iivV(v)v0,v1,,vkvkI(vk)V(v0)++V(vk)

使用上述技术的变体很容易支持get和set操作。基本上,当我们向下遍历树时,我们会跟踪运行中的值之和,因此对于遍历访问的每个节点,我们将知道从根到的路径上节点的值之和。一旦这样做,对上述get和set实现的简单调整就足够了。xx

现在我们可以有效地支持。首先,我们将间隔为与树中某些节点集相对应的间隔的并集如果需要,在左端点和右端点处拆分一个节点) ),与设置操作的第1-3步完全相同。现在,我们只需将添加到存储在每个节点中的值即可。(我们不会删除其后代。)add([a,b],δ)[a,b]O(lgn)O(lgn)δO(lgn)

这提供了一种在每次操作的时间内支持获取,设置和添加的方法。实际上,每个操作的运行时间为,其中表示设置操作的数量加上加法操作的数量。O(lgn)O(lgmin(n,s))s

支持刺伤操作

刺刺查询是最困难的支持。基本思想是修改以上数据结构以保留以下附加不变式:

(*)与每个叶子对应的间隔是最大平坦间隔。I()

在这里我说,如果(i)是平坦的,并且(ii)没有包含间隔是平坦的,则间隔是最大平坦间隔(换句话说,对于所有满足,或不平坦)。[a,b][a,b][a,b]a,b1aabbn[a,b]=[a,b][a,b]

这使stab操作易于实现:

  • stab(i)查找其间隔包含的叶子,然后返回该间隔。i

但是,现在我们需要修改集合并添加操作以保持不变(*)。每次我们将一片叶子分成两部分时,如果一些相邻的叶子间隔对具有相同的函数值,则可能违反不变式。幸运的是,每个设置/添加操作最多添加4个新的叶子间隔。同样,对于每个新间隔,很容易在其左侧和右侧立即找到叶子间隔。因此,我们可以判断是否违反了该不变式。如果是,则我们合并相邻间隔,其中具有相同的值。幸运的是,合并两个相邻的间隔不会触发级联更改(因此,我们不需要检查合并是否可能引入了对不变式的其他违反)。总之,这涉及检查ff12=O(1)对间隔并可能合并它们。最后,由于合并会改变树的形状,因此,如果这违反了平衡不变式,请执行任何必要的旋转以使树保持平衡(遵循使二进制树保持平衡的标准技术)。总共,这最多会将额外工作添加到设置/添加操作中。O(lgn)

因此,此最终数据结构支持所有四个操作,每个操作的运行时间为。更精确的估计是每个操作时间,其中计数设置和添加操作的数量。O(lgn)O(lgmin(n,s))s

离别的想法

ew,这是一个非常复杂的方案。我希望我没有犯任何错误。在使用此解决方案之前,请仔细检查我的工作。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.