用于查找间隔之间交集的Java算法

时间:2014-07-01 13:16:28

标签: java algorithm

我有这样的时间间隔:

[5,10]

我有更多的时间点列表,长度不同,例如:

t1=[3,6,9,10] 
t2=[2,4,5,6,10]
..

其中t1 [3,6]是第一个区间,[6,9]是第二个区间,依此类推。

t2和其他列表也是如此。

现在我需要保存列表,以及与第一个时间间隔相交的特定间隔。例如,在t1[3,6][5,10], [6,9]相交的[5,10]First time interval <- [t1,t2] For each list For each time interval [s1,s2] in list if(s1>= t1 && s2 <= t2) { saveIntervall() } else if (s1<= t2 && s2 > t2) { saveIntervall() } else if(s1 < t1 && s2 >= t1) { saveIntervall() } else if (s1 < t1 && s2 > t2) { saveIntervall() } 等相交。

我已经制作了一个算法,但我处理更多数据,我需要一个快速算法。 例如,如果我使用300.000列表并且每个列表具有200个时间点,则我的算法1在大约5-10秒内很好。但是,如果我有10.000或更多的时间点,算法非常慢。

我的算法是这样的:

            double score = 0.0d;
        if(s1>= t1 && s2 <= t2)
        {
            score = (s2-s1) / (t2-t1);
        }
        else if(t2 >= s2 && t1 > s1)
        {
            score = (s2-t1) / (t2-t1);
        }
        else if(t2 < s2 && t1 <= s1)
        {
            score = (t2-s1) / (t2-t1);
        }
        else
        {
            score = 1;
        }

Edit1:是的我订购了清单。

Edit2:我还有另外一个问题,当我找到intersaction时,我需要计算交点大的0到1之间的分数。 我用这个:

{{1}}

我也可以加快速度吗?

3 个答案:

答案 0 :(得分:5)

两个区间[s1,s2]和[t1,t2]的交集为空当且仅当

    t2 < s1 or s2 < t1

因此,要检查两个是否相交,需要两个间隔,您只需要进行上述测试。

一旦你知道s2&lt; t1然后没有必要继续在带有t1的列表上继续,因为更大的间隔永远不会相交,这意味着你应该继续前进。

Naive Psuedo算法:

   given [s1, s2]
   for each list [t1, t2, ... t(n)] in search_lists
        for each interval [t(x), t(x+1)] from [t1, t2, ... t(n] (x goes from 0 to n-1)
           if t(x+1) < s1
              continue
           if s2 < t(x)
              break
           saveInterval()

这可以改进很多,以确实使用[t1,t2,..,t(n)]排序的事实。

首先请注意,[s1, s2]将与[t(x), t(x+1)] iff t(x+1) >= s1s2 >= t(x)

相交

然而

if t(x) >= s1 then for every h>0      `t(x+h) >= s1` 

if s2 >= t(x) then for every h>0  `s2 >= t(x-h)`

所以如果我们找到最小的i使得t(i + 1)> = s1那么来自[t(i), t(i+1)] on-wards的所有区间都满足第一个交叉条件;即([t(i+1), t(i+2)][t(i+2), t(i+3)] ...)

并且如果我们找到最大的j使得s2> = t(j-1),则来自[t(j-1), t(j)]的所有间隔向后满足第二条件。即([t(j-2), t(j-1)][t(j-3), t(j-2)] ...)

i和j之间的所有间隔都符合两个标准,只有它们。

所以最终的算法是:

given [s1, s2]
for each list [t1, t2, ... t(n)] in search_lists
    find the smallest i such that t(i+1)>=s1  
    find the biggest  j such that s2>= t(j-1)

    if j>i then all the intervals between `{t(i)... t(j)}` intersect with [s1, s2]
    otherwise there is no intersection.       

由于{t1, t2, t3...t(n)}已排序,我们可以使用二进制搜索有效地查找索引ij

EDIT2:

[s1,s2]和[t1,t2]的交点是:
  [max(s1, t1), min(s2,t2)]

尺寸为: L1 = s2-s1 L2 = t2-t1 L3 = min(s2,t2) - max(s1,t1)

您要找的分数为:L3/ min(L2, L1) 0到1之间的分数。

(min(s2,t2) - max(s1,t1)) / ( min(s2-s1, t2-t1) )

计算这个的成本是3次测试,3次减去操作和一次浮点操作。 但我假设间隔是有效的,交叉存在,否则需要更多的测试。 (s2>s2t2>t1min(s2,t2) > max(s1,t1)。最终测试与上述讨论相同的 iff 条件相同。

答案 1 :(得分:2)

首先,您的数据结构令人困惑 - 如果您试图谈论离散的时间间隔,请按照这样的方式构建数据;例如int[][],其中内部数组的长度始终为2,因此t1变为:

int[][] t1 = {{3,6}, {6,9}, {9,10}};

使用正确的结构可能会帮助您简化算法并使其更易于使用。


然而,比正确结构化的数组更好的方法是使用专用类型来表示这些间隔,这样您就可以传递List<Interval>个对象并对它们进行某种包含检查。但是不要重新发明轮子。令人敬畏的Guava library提供了一个可以使用的强大的Range类。尽管如此,它还提供了RangeSetRangeMap类,让您轻松完成您所谈论的内容。另请参阅他们的Ranges Explained文章,其中涵盖了基础知识。

请注意,如果您无法在外部重新设计阵列结构,则可以在内部轻松将当前设计转换为Range个对象。

我曾试图建立自己的IntervalSet课程,让我告诉你,要做到这一点是一个棘手的问题,而且你会使用他们精心设计的设计来节省很多麻烦和高度测试的范围实用程序。

以下是我用Guava描述的方式 - 注意我们甚至不需要思考所涉及的数学 - Range对我们来说是对的:

/**
 * Given a Range and an group of other Ranges, identify the set of ranges in
 * the group which overlap with the first range.  Note this returns a Set<Range>
 * not a RangeSet, because we don't want to collapse connected ranges together. 
 */
public static <T extends Comparable<?>> Set<Range<T>>
        getIntersectingRanges(Range<T> intersects, Iterable<Range<T>> ranges) {
    ImmutableSet.Builder<Range<T>> builder = ImmutableSet.builder();
    for(Range<T> r : ranges) {
        if(r.isConnected(intersects) && !r.intersection(intersects).isEmpty()) {
            builder.add(r);
        }
    }
    return builder.build();
}

/**
 * Given a 2-length array representing a closed integer range, and an array of
 * discrete instances (each pair of which therefore represents a closed range)
 * return the set of ranges overlapping the first range.
 * Example: the instances array [1,2,3,4] maps to the ranges [1,2],[2,3],[3,4].
 */
public static Set<Range<Integer>> getIntersectingContinuousRanges(int[] intersects,
        int[] instances) {
    Preconditions.checkArgument(intersects.length == 2);
    Preconditions.checkArgument(instances.length >= 2);
    ImmutableList.Builder<Range<Integer>> builder = ImmutableList.builder();
    for(int i = 0; i < instances.length-1; i++) {
        builder.add(Range.closed(instances[i], instances[i+1]));
    }
    return getIntersectingRanges(Range.closed(intersects[0], intersects[1]),
                                 builder.build());
}

使用您的示例:

public static void main(String[] args)
{
    int[] interval = {5,10};
    int[] t1 = {3,6,9,10};
    int[] t2 = {2,4,5,6,10};

    System.out.println(getIntersectingContinuousRanges(interval, t1));
    System.out.println(getIntersectingContinuousRanges(interval, t2));
}

以上打印出来:

[[3‥6], [6‥9], [9‥10]]
[[4‥5], [5‥6], [6‥10]]

答案 2 :(得分:0)

给定左边界和两个间隔的长度,交叉点可以通过以下代码进行测试。

protected boolean intervalOverlap( int pos1, int length1, int pos2, int length2 ){
  int pos_A_left  = pos1;
  int pos_A_right = pos1 + length1;
  int pos_B_left  = pos2;
  int pos_B_right = pos2 + length2;
  return pos_B_left < pos_A_right && pos_A_left < pos_B_right;
}

有一个简短的article,其中讨论了这种方法。此外,还提供了间隔的替代表示(使用中心和长度),可以更有效地实现交叉测试。