处理封闭间隔的数据结构

时间:2012-12-11 22:19:52

标签: algorithm data-structures

当我发现无法处理的问题时,我正在学习测试:

  

设计处理(关闭)间隔的数据结构     将提供三个操作:

     

插入(x,y) - 添加区间[x,y]

     

删除(x,y) - 删除区间[x,y]

     

Count(x,y) - 计算区间[x,y]

中纯粹包含的区间数      

x,y是从1到n的整数。所有操作最多可以花费O((log n) 2

有人可以帮忙吗?

4 个答案:

答案 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中所有区间的集合

  1. 包含Int(v)和
  2. 不包含Int(parent(v))。
  3. 在每个顶点v中,我们只将Span(v)中间隔的最右端点存储在由此端点排序的自平衡二进制树中,并且每个节点都是增加了后代节点的数量。 即"外部"的每个节点树包含一个"内部" 这是必要的,这使我们能够有效地计算完全包含给定查询间隔的间隔数。

    段树上的基本查询操作是确定包含给定点x的区间集,并且这很容易更改为计数而不是报告单个区间。操作很简单:将v设置为root,

    1. 将Span(v)中的间隔数添加到总数中。
    2. 如果v是叶子,则停止。
    3. 否则,假设v的左右孩子分别是u和w。如果x包含在Int(u)中,则递归到u,否则递归到w。
    4. 给定查询间隔(a,b),上述查询可以执行两次,一次为a,一次为b。将nStraddlingAtLeastOne设置为两个查询的计数总和。请注意,对于每个节点,可以在O(1)时间内完成计数 - 它只是存储Span(v)的自平衡二叉树的根节点的计数字段。

      双十字路口!

      难度(​​以及最终阻碍我花费一些时间的O(log n)算法的早期尝试)是我们还需要计算同时跨越两者的间隔数查询的端点(a,b)。为此,我们再次从root开始,并对(查询的起点)执行修改后的查询,其中步骤1替换为:

      1. 计算具有右端点> = b的Span(v)中的间隔数,并将其添加到总数中。
      2. 由于自平衡二叉树,该计数步骤可以在O(log n)时间内执行。由于每个树级别最多需要2个这些计数操作,因此这是将时间复杂度推高到O(log ^ 2 n)的部分。将nContaining设置为此查询的总计。我们可以使用

        计算nStraddlingOne
          

        nStraddlingOne = nStraddlingAtLeastOne - nContaining

        使用nStraddlingOne和从Fenwick树计算的h,我们现在可以根据顶部的等式计算nContained。

        添加和删除间隔

        更新也是O(log ^ 2 n),因为更新Fenwick树是O(log n),并且向段树添加间隔(x,y)需要使用O(log ^ 2 n)时间以下算法,从根处的v开始:

        1. 如果(x,y)包含Int(v),则将y添加到存储Span(v)右端点的自平衡二叉树中并停止。
        2. 否则,假设v的左右孩子分别是u和w。
          • 如果(x,y)与Int(u)重叠,则递归到u。
          • 如果(x,y)与Int(w)重叠,则递归w。
        3. 上述遍历仅访问分段树中的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