当我发现无法处理的问题时,我正在学习测试:
设计处理(关闭)间隔的数据结构 将提供三个操作:
插入(x,y) - 添加区间[x,y]
删除(x,y) - 删除区间[x,y]
Count(x,y) - 计算区间[x,y]
中纯粹包含的区间数x,y是从1到n的整数。所有操作最多可以花费O((log n) 2 )
有人可以帮忙吗?
答案 0 :(得分:4)
这可以在O(log ^ 2 n)时间和O(nlog n)空间中使用 Fenwick Tree 和基于的数据结构的组合来解决segment tree ,其中包含每个节点内的内部树。前者用于有效地查找和更新给定范围内的点数;后者用于有效地查找和更新跨越给定点的段的计数。基本思想是计算给定查询范围内的段端点,然后调整跨越任一端点或两个端点的段。
该算法基于以下观察:对于给定的查询范围(a,b),
nContained =(h - nStraddlingOne)/ 2
其中nContained是(a,b)包含的区间数,h是(a,b)中任意一种(开始或结束)的区间端点数,nStraddlingOne是包含任一区间的数量。只有一个或只有一个。 "数据库"任何时候的间隔在这里称为D.
Fenwick树允许您使用O(n)表计算O(log n)时间内两个整数索引(a,b)之间的总点数。它还允许O(log n)插入和删除。我们将每个间隔的两个端点添加到它,并使用它来计算O(log n)时间中的h。
我们的其他数据结构与段树非常相似,但有两个主要区别:我们不是从输入的一组区间推断区间的起点和终点,而是将端点集合作为每个可能的整数介于1和n之间,并且没有"开放集" (这是为了简化以后添加新的间隔);并且我们以特定方式存储每个节点的间隔。
为简单起见,假设n是2的幂(如果它不是,则只选择2的下一个最大幂 - 这将导致小于n的增加,因此时间复杂度赢得了'改变)。令T为完整的二叉树,其具有用于每个位置1< = i< = n的叶节点。 (该树总共有2n - 1个节点。)每个叶节点代表一个单独的位置。每个非叶节点表示其下所有叶节点的位置的并集,其必须形成长度为2的幂的区间。调用由节点v Int(v)表示的区间。 (注意:因为这个二叉树是完整的,所以它可以表示"隐含地"二进制堆通常是这样,以节省空间。)
对于T的每个节点v,对应一组称为Span(v)的间隔。 (我们实际上只会存储他们最右边的端点。)Span(v)是D中所有区间的集合
在每个顶点v中,我们只将Span(v)中间隔的最右端点存储在由此端点排序的自平衡二进制树中,并且每个节点都是增加了后代节点的数量。 即"外部"的每个节点树包含一个"内部" 这是必要的,这使我们能够有效地计算完全包含给定查询间隔的间隔数。
段树上的基本查询操作是确定包含给定点x的区间集,并且这很容易更改为计数而不是报告单个区间。操作很简单:将v设置为root,
给定查询间隔(a,b),上述查询可以执行两次,一次为a,一次为b。将nStraddlingAtLeastOne设置为两个查询的计数总和。请注意,对于每个节点,可以在O(1)时间内完成计数 - 它只是存储Span(v)的自平衡二叉树的根节点的计数字段。
难度(以及最终阻碍我花费一些时间的O(log n)算法的早期尝试)是我们还需要计算同时跨越两者的间隔数查询的端点(a,b)。为此,我们再次从root开始,并对(查询的起点)执行修改后的查询,其中步骤1替换为:
由于自平衡二叉树,该计数步骤可以在O(log n)时间内执行。由于每个树级别最多需要2个这些计数操作,因此这是将时间复杂度推高到O(log ^ 2 n)的部分。将nContaining设置为此查询的总计。我们可以使用
计算nStraddlingOnenStraddlingOne = nStraddlingAtLeastOne - nContaining
使用nStraddlingOne和从Fenwick树计算的h,我们现在可以根据顶部的等式计算nContained。
更新也是O(log ^ 2 n),因为更新Fenwick树是O(log n),并且向段树添加间隔(x,y)需要使用O(log ^ 2 n)时间以下算法,从根处的v开始:
上述遍历仅访问分段树中的O(log n)个节点,因为添加的每个间隔将出现在任何树级别最多2个节点的间隔集中,每个最大2log(n)空间使用量间隔。请参阅the Wikipedia page on Segment Trees以获取证明和进一步说明。删除间隔使用类似的算法。在节点处每次插入或移除到自平衡二叉树时需要O(log n)时间,总计为O(log ^ 2 n)时间。
空间使用是O(nlog n),因为在段树中存在O(log n)树级别,每个树级别可能需要空间用于包含每个可能端点的内部树节点的2个实例。特别要注意的是,即使存在O(n ^ 2)个可能的不同区间,我们也只存储每个区间的最右端点,因此这不是问题。此外,因为我们存储计数,添加现有间隔的第二个副本只会导致计数增加 - 它不会导致任何新的节点分配。 Fenwick树仅使用O(n)空间。
答案 1 :(得分:1)
见Range Tree。查询的时间复杂度被称为O(log d n + k)。这里d是维度,n是树中存储的点数,k是查询点报告的点数。
但我们只需要计算而不报告积分。因此,我认为如果在每个节点上维持子节点数(实际上叶子的数量,因为真实点存储在叶子中),则可以消除该k,留下O(log 2 n)。插入和删除也是O(log 2 n)。
答案 2 :(得分:0)
一个区间树适合这里,例如https://github.com/ekg/intervaltree/blob/master/IntervalTree.h看到这个代码,它提供了一个看起来像O(log(n))的findContained,除了那个删除更复杂但是肯定可以在O(log)中完成(n))的 http://en.wikipedia.org/wiki/Interval_tree#Deletion
答案 3 :(得分:0)
有时可以使用简单的排序集:
- 数据集中的间隔具有一些已知的最大长度,
- 此外,与集合中x个数据点的总值范围相比,此最大长度不会太大。
在这种情况下,只需使用简单的有序集就足够了:
- 仅使用间隔的x dimension
为其添加间隔。
- 像这样查询:get intervals with their x dimension in range [x', y'] and then filter out the few intervals that have their y dimension crossing the y' limit
。
鉴于具有已知最大长度的数据属性的上述相同前提条件,此逻辑可以扩展为执行区间树类查询,其中查询不在“区间”内要求“数据纯粹包含”,而是要求“数据重叠给定间隔”。在这种情况下,查询将是:
- get intervals with their x dimension in range [x' - maximum interval range, y' + maximum interval range] and then filter out the few intervals that actually are outside of requested range
。