【问题标题】:Algorithm to produce a difference of two collections of intervals产生两个间隔集合的差异的算法
【发布时间】:2012-08-09 20:03:26
【问题描述】:

问题

假设我有两个区间集合,分别命名为 A 和 B。我如何以最节省时间和内存的方式找到差异(相对补码)?

图片说明:

区间端点是 整数 ( ≤ 2128-1 ) 并且它们总是 2n 长并且在 m×2 上对齐n lattice(这样你就可以用它们制作一棵二叉树)。

输入中的间隔可以重叠,但这不会影响输出(如果展平,结果是相同的)。

问题是因为两个集合中有很多间隔(最多 100,000,000),所以幼稚的实现可能会很慢。

输入是从两个文件中读取的,并以这样一种方式排序,即较小的子间隔(如果重叠)按大小顺序紧随其父级之后。例如:

[0,7]
[0,3]
[4,7]
[4,5]
[8,15]
...

我尝试了什么?

到目前为止,我一直在研究一种生成二叉搜索树的实现,同时聚合两个集合中的相邻区间 ([0,3],[4,7] => [0,7]),然后遍历第二棵树并“排除”存在的区间在两者中(如有必要,在第一棵树中细分较大的间隔)。

虽然这似乎适用于小型集合,但它需要越来越多的 RAM 来保存树本身,更不用说完成插入和移除树所需的时间了。

我想既然间隔是预先排序的,我可以使用一些动态算法并一次性完成。不过,我不确定这是否可行。


那么,我该如何有效地解决这个问题呢?

免责声明:这不是作业,而是对我面临的实际问题的修改/概括。我正在使用 C++ 编程,但我可以接受任何 [命令式] 语言的算法。

【问题讨论】:

  • 如何进行简单的线性一次性搜索?似乎需要更少的内存,特别是如果您一次只存储一对间隔。
  • +1,@H2CO3。感觉这个问题和finding the intersection of two sorted arrays有点类似,所以这里可以应用线性搜索。
  • @Kevin 是的,基本上我只是想 KISS 和 b-tree 似乎有点开销。
  • @H2CO3 是的,这就是我的想法,但问题是间隔不需要完全匹配,而可以仅仅是彼此的子间隔。
  • 也许你可以做第一次,丢弃或合并所有子间隔。

标签: c++ algorithm language-agnostic binary-tree


【解决方案1】:

回想一下我们在学校的第一个编程练习——编写一个计算器程序。从输入行获取算术表达式,对其进行解析和评估。还记得跟踪括号深度吗?所以我们开始吧。

类比:区间起点是左括号,终点是右括号。我们跟踪括号深度(嵌套)。二相交的深度,一差的深度

算法:

  • 无需区分A和B,只需将所有起点和终点按升序排序即可
  • 将括号深度计数器设置为零
  • 遍历端点,从最小的一个开始。如果是起点,则增加深度计数器,如果是终点,则减少计数器
  • 跟踪深度为 1 的区间,即 A 和 B 差异的区间。深度为二的区间为AB交点

【讨论】:

  • 这是一个很好的解决方案!我将不得不为 A 和 B 保留两个计数器,因为多个间隔可以在两者中重叠,我希望它们计数为一个,否则我可以计算 A ∩ B、A - B、B - A 和 A ∪ B,所有一口气!
  • 我只想添加深度!= 0 给出联合。
  • 除非我误解了这一点,否则这不是区别,它只是异或。如果有一个区间在 A 中不存在,但在 B 中存在,则深度为 1 将产生它。
【解决方案2】:

您的间隔已排序,这很棒。您可以在几乎没有内存的情况下在线性时间内完成此操作。

首先“压平”你的两组。即对于集合 A,从最低间隔开始,并结合任何重叠的间隔,直到你有一个没有重叠的间隔集。然后为 B 执行此操作。

现在拿起你的两组,从前两个间隔开始。我们将这些称为 A 和 B、Ai 和 Bi 的区间索引。

Ai 索引 A 中的第一个区间,Bi 索引 B 中的第一个区间。

虽然有一些时间间隔需要处理,但请执行以下操作:

考虑两个区间的起点,起点是否相同?如果是这样,将两个区间的起点提前到较小区间的终点,则不向您的输出发出任何内容。将较小区间的索引推进到下一个区间。 (即如果 Ai 在 Bi 之前结束,则 Ai 前进到下一个区间。)如果两个区间在同一位置结束,则 Ai 和 Bi 都前进并且什么都不发射。

一个起点是否早于另一起点?如果是这样,则发出从较早起点到 a) 较晚端点的起点或 b) 较早终点的终点的间隔。如果您选择选项 b,则将前一区间的索引提前。

因此,例如,如果 Ai 处的间隔首先开始,则您发出从 Ai 开始到 Bi 开始的间隔,或者 Ai 的结束,以较小者为准。如果Ai在Bi开始之前结束,你推进Ai。

重复直到用完所有间隔。

附言。我假设您没有多余的内存来将两个间隔集展平为单独的缓冲区。在两个函数中执行此操作。一个“获取下一个区间”函数,它推进区间索引,根据需要进行展平,并将展平的数据提供给差分函数。

【讨论】:

    【解决方案3】:

    您正在寻找的是Sweep line algorithm。 一个简单的逻辑应该告诉您扫描线何时与 A 和 B 中的一个区间相交,以及它仅与一个集合相交的位置。

    这与problem 非常相似。假设您有一组垂直线穿过 B 段的端点。

    这个算法的复杂度是 O((m+n) log (m+n)) ,这是初始排序的成本。排序集上的扫线算法需要O(m+n)

    【讨论】:

      【解决方案4】:

      我认为你应该使用 boost.icl (Interval Container Library) http://www.boost.org/doc/libs/1_50_0/libs/icl/doc/html/index.html

      #include <iostream>
      #include <boost/icl/interval_set.hpp>
      
      using namespace boost::icl;
      
      int main()
      {
        typedef interval_set<int> TIntervalSet;
        TIntervalSet intSetA;
        TIntervalSet intSetB;
      
        intSetA += discrete_interval<int>::closed( 0, 2);
        intSetA += discrete_interval<int>::closed( 9,15);
        intSetA += discrete_interval<int>::closed(12,15);
      
        intSetB += discrete_interval<int>::closed( 1, 2);
        intSetB += discrete_interval<int>::closed( 4, 7);
        intSetB += discrete_interval<int>::closed( 9,10);
        intSetB += discrete_interval<int>::closed(12,13);
      
        std::cout << intSetA << std::endl;
        std::cout << intSetB << std::endl;
      
        std::cout << intSetA - intSetB << std::endl;
      
        return 0;
      }
      

      打印出来

      {[0,2][9,15]}
      {[1,2][4,7][9,10][12,13]}
      {[0,1)(10,12)(13,15]}
      

      【讨论】:

      • 不错的解决方案,我不知道 ICL。但是,这似乎是在 RAM 中运行的,因此它并不能真正扩展到数亿个间隔。
      猜你喜欢
      • 2016-07-31
      • 1970-01-01
      • 1970-01-01
      • 2021-07-25
      • 1970-01-01
      • 2017-05-04
      • 2012-09-18
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多