如何在Java中有效地找到两个范围列表中的所有重叠

时间:2013-01-16 20:53:41

标签: java range

我在查找两个列表中的所有重叠范围时遇到问题 此问题与This问题类似,但输入不同。

我有2个输入文件,一个包含多行范围和数据对,另一个包含用于查找交叉点的范围列表。

我已经编写了一个文件阅读器类,它从数据文件中读取,一次返回一个对象,其中包含范围和数据对的列表,但是当我试图找到两者的重叠时遇到了麻烦范围列表。

目前我正在做的是强制它,将数据列表中的每个范围与交叉点列表中的每个其他范围进行比较,但由于数据文件非常大,所以需要很长时间时间。

示例对象:
这是数据列表中的对象:

public DataModel {
    private int start; {set; get;}
    private int end; {set; get;}
    //Other Data
}

范围模型只是成对整数的列表(开始,结束)。

while (fileParser.hasNext()) {
    dataList = fileParser.next();
    for (DataModel data : dataList)
        for (RangeModel range : rangeList)
            if(overlaps(data, range))
                print(range.getString + " " + data.getString);
}

为清晰起见编辑:

DataModel以较小长度的相似范围的较小数据包给出,但它们大多在20以下,因此比较将在相同的RangeModel和每个新的DataModel上重复运行。 所有数据的总范围约为20亿,但这并不重要。谢谢你的帮助。

4 个答案:

答案 0 :(得分:1)

我可以考虑不同的优化,但它们取决于检查后您想要的数据类型。

对数据和范围进行排序并按顺序处理它们可以立即提高性能,因为测试从100开始的范围与以50结尾的另一个范围是没有意义的。

另一个改进是“压缩”范围。如果您有(1-10),(10-20),(20-30)等范围,那么您可以轻松地用单个(1-30)范围替换它们,并减少测试次数。您可以创建一个适当的AggregateRange类,用于跟踪其组成范围的标识,以防您仍然想知道哪个原始范围导致重叠。

另一项改进是在处理数据列表时巧妙地使用以前的结果。例如:假设您测试数据范围(1-10)并且它恰好不重叠。如果下一个测试数据范围为(2-8),则不需要针对范围进行测试,因为您之前的结果保证它不会重叠。

这种改进背后的基本思想是推进任何未经测试的数据范围的开始,直至并包括最后一个非重叠数据范围的结束。如果新的开始超过其自己的结束,则不需要测试,因为它不重叠。 这意味着非重叠(1-20)应该将未经测试的(10-100)转换为未经测试的(20-100)。这可能比较难实现,所以要小心不要过度。

答案 1 :(得分:1)

检查我的理解是否正确:

  • DataModel和RangeModel表示范围。 (DataModel可能包含更多数据,但它无关紧要)。
  • 有约。 200万DataModel s,以及少量RangeModel s。 (尽管我的解决方案没有利用这种不对称性)
  • 有必要将DataModel中的范围保留为不同的实体,即使它们是重叠的。 (如果您只对交叉点感兴趣,可以在它们彼此靠近时折叠范围作为优化)。

我要描述的方法可以在2个范围列表之间进行范围交叉,无论范围如何(重叠,大范围等)。限制是2个范围列表的大小之和(排序是瓶颈),以及找到的范围数(迭代是瓶颈)。

将范围拆分为2个EndPoint个对象,表示:值(int),范围的开始或结束(boolean),起始EndPoint对象(范围开始时的null;指向范围开始的EndPoint对象(如果范围结束),标记(int,标记是数据还是范围查询)。

将两个范围列表中的所有EndPoint放在一起,按值对它们进行排序,通过将start放在结束端点的前面来打破中断(如果您考虑触摸交叉点)。排序步骤的复杂性是O((m + n)log(m + n))。

根据此伪代码循环遍历排序的EndPoint

open_data = HashSet()
open_range = HashSet()

for e in endpoints:
  if e is start of range:
    if e is data:
      print e intersect with all in open_range
      open_data.add(e)
    else: // e is range to test
      print e intersect with all in open_data
      open_range.add(e)
  else: // e is end of range
    if e is data:
      open_data.remove(e.startPoint)
    else: // e is range to test
      open_range.remove(e.startPoint)

从HashSet中添加和删除是分摊O(1)。问题在于打印交叉点,即O(k),其中k是交叉点的数量,在最坏的情况下可以达到O(m * n)。

组合,在最坏的情况下复杂度为O((m + n)log(m + n)+ m * n)。您可以根据数据的属性做得更好。这是一个非常通用的解决方案。

答案 2 :(得分:0)

理想的解决方案取决于数据的具体特征,但排序两个输入集将是一个很好的第一步,这将使您减少所需的比较量。

一个选项是从min(startTime)到max(endTime)创建一个数组,并在每个位置存储对覆盖此范围的输入值的引用。

因此,如果您输入的是  答:[1-5]和B:[3-7],你可以有一个看起来像

的数据结构
1: A
2: A
3: A,B
4: A,B
5: A,B
6: B
7: B

然后,为了测试[2-4]与数据集的交集,您只需在数组列表中查找2,3,4并连接结果。

如果您只关心IF是否有交叉点,而不是交叉口的确切位置,则可以进一步提高速度。或者,如果您只关心AN交叉点,而不是所有交叉点。

答案 3 :(得分:0)

您可以对每个范围列表o(N ln N)进行排序,并对这些范围进行合并排序O(N)

这将显示任何重叠范围,并且CPU时间最短。