【问题标题】:How to find all overlaps in two lists of ranges efficiently in Java如何在Java中有效地找到两个范围列表中的所有重叠
【发布时间】:2013-01-16 20:53:40
【问题描述】:

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

我有 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 亿,但这并不重要。感谢您的帮助。

【问题讨论】:

  • 只需将所有范围混合在一起,用一些标记来指示是数据还是要查找重叠的范围。
  • 文件中每条记录的逻辑顺序是否有保证?例如,通过同时迭代两个输入,一对逻辑排序的列表可以更容易、更有效地在交叉点合并。
  • 不保证顺序,但都会有相对顺序。 fileParser 可以一个接一个地返回两个重叠的列表,但是这些列表是按列表中所有范围的并集的起始范围排序的。
  • 那么DataModel和RangeModel是一样的吗?两个文件一共有多少个范围?

标签: java range


【解决方案1】:

我可以想到不同的优化,但它们取决于您希望在检查后获得什么样的数据。

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

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

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

这项改进背后的基本理念是将任何未经测试的数据范围的起点提前到最后一个非重叠数据范围的终点并包括在内。如果新的开始超过它自己的结束,则不需要测试,因为它不重叠。 这意味着非重叠 (1-20) 应将未经测试的 (10-100) 转换为未经测试的 (20-100)。这可能难以实现,因此请注意不要过度。

【讨论】:

  • 如果范围可以每个列表可以重叠,那么就很难对它们进行排序。
  • 一点也不。您只需要定义一个适当的方法来比较范围。我的 自然 方式是先测试开始再开始,然后结束对结束。这不是对范围进行排序的唯一方法,但我认为它在这种情况下最有用。这意味着 (1-10)
  • @MartínValdésdeLeón:如果我将 (12,99) 添加到您的示例中,那么扫描列表并检查先前范围的终点是否已结束将非常低效。我会将一个范围视为 2 个实体(2 个端点)并对其进行排序。
  • 由于文件格式的原因,对数据范围进行排序是不可能的,但我可以对测试范围进行排序。我会尝试压缩测试范围,但测试范围不能保证重叠。可以有任意数量的测试范围。
  • 我相信您的建议是先按结束排序,然后按开始排序。这样最终的排序将是 (1-10) natural 替代品,其中一个和我建议的第一个。它们肯定会影响改进的性能,但我不知道只看哪一个是最好的。这可能取决于改进的具体实施。
【解决方案2】:

检查我的理解是否正确:

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

我将要描述的方法可以在 2 个范围列表之间进行范围交集,而不管范围看起来如何(重叠、大范围等)。限制是 2 个范围列表的大小之和(排序是瓶颈)和找到的范围数量(迭代是瓶颈)。

将范围分割成2个EndPoints对象,分别表示:值(int)、范围开始或结束(boolean)、起始EndPoint对象(null在范围开始;指向EndPoint对象,如果范围结束则表示范围的开始),标记(int,标记它是数据还是要查询的范围)。

将两个范围列表中的所有EndPoints 放在一起,按值对它们进行排序,通过将 start 放在 end 端点的前面来打破平局(如果您认为触摸是交叉点)。排序步骤的复杂度是O((m + n)log(m + n))。

根据这个伪代码循环通过排序的EndPoints:

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)。根据数据的属性,您也许可以做得更好。这是一个非常通用的解决方案。

【讨论】:

  • 很好的答案。最后一行应该是open_range.remove(e.startPoint),我想。
  • 对不起,不够清晰,但 DataModel 以不同长度的相似范围的较小数据包给出,但它们大多在 20 以下,因此比较将在同一个 RangeModel 上重复运行,每个新的数据模型。 所有数据的总范围约为 20 亿,但这并不重要。感谢您的帮助。
【解决方案3】:

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

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

因此,如果您的输入是
A: [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 并将结果连接起来。

如果您只关心是否存在交叉路口,而不是确切的路口所在位置,则可以进一步提高速度。或者,如果您只关心一个十字路口,而不是所有十字路口。

【讨论】:

  • 这种方法的主要问题是有大量可能的位置要为其创建数组。输入位置从 0 到 Integer.max。不幸的是,我正在寻找范围列表和数据列表之间的所有交集。
  • 如果数据的总范围是稀疏填充的,那么 SparseArray 实现似乎很适合这种情况。
【解决方案4】:

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

这将以最少的 CPU 时间显示任何重叠范围。

【讨论】:

    猜你喜欢
    • 2018-12-11
    • 2013-02-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-10-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多