Java中的范围查找

时间:2011-11-18 15:25:54

标签: java algorithm search data-structures

假设我有一个未排序的重叠ranges数组。每个range只是一对整数beginend。现在我想查找给定的key是否属于ranges中的至少一个。也许,我必须知道它所属的ranges

我们可以假设ranges数组需要大约1M并且适合内存。我正在寻找一种简单的算法,它只使用标准的JDK集合,没有任何3d派对库和特殊的数据结构,但工作速度相当快。

你会建议什么?

5 个答案:

答案 0 :(得分:5)

按自定义Comparator以数字方式对范围进行排序,然后为每个键 k 构建单元素范围[ k k ]并使用不同的Comparator为此范围执行binary search

搜索Comparator的{​​{1}}应该返回

  • compare(x,y) if <0
  • x.max < y.min if >0
  • 否则
  • x.min > y.max(它的两个范围参数重叠)。

如@Per所述,您需要一个不同的,更严格的0进行排序,但前两个条款仍然有效。

即使范围重叠,这也应该有效,但您可能希望在排序后合并重叠范围以加快搜索速度。合并可以在O( N )时间内完成。

这实际上是静态interval tree,即没有O(lg N )插入或删除的静态{{3}},就像排序数组可以被认为是静态二进制搜索树一样

答案 1 :(得分:3)

我相信这就是您所寻找的:http://en.wikipedia.org/wiki/Interval_tree

但请先查看这个更简单的解决方案,看看它是否符合您的需求:Using java map for range searches

答案 2 :(得分:3)

如果您不需要知道哪个区间包含您的观点(编辑:我猜您可能会这样做,但我会留下这个答案给其他有这个问题的人没有),然后

  1. 通过计算两个数组B和E来预处理间隔.B是按排序顺序的begin值。 E是按排序顺序结束的值。

  2. 为了查询点x,使用二分搜索来找到最小索引i,使得B [i]> x和最小索引j使得E [j]≥x。包含x的区间[begin,end]的数量是i-j。


  3. class Interval {
        double begin, end;
    }
    
    class BeginComparator implements java.util.Comparator<Interval> {
        public int compare(Interval o1, Interval o2) {
            return Double.compare(o1.begin, o2.begin);
        }
    };
    
    public class IntervalTree {
        IntervalTree(Interval[] intervals_) {
            intervals = intervals_.clone();
            java.util.Arrays.sort(intervals, new BeginComparator());
            maxEnd = new double[intervals.length];
            initializeMaxEnd(0, intervals.length);
        }
    
        double initializeMaxEnd(int a, int b) {
            if (a >= b) {
                return Double.NEGATIVE_INFINITY;
            }
            int m = (a + b) >>> 1;
            maxEnd[m] = initializeMaxEnd(a, m);
            return Math.max(Math.max(maxEnd[m], intervals[m].end), initializeMaxEnd(m + 1, b));
        }
    
        void findContainingIntervals(double x, int a, int b, java.util.Collection<Interval> result) {
            if (a >= b) {
                return;
            }
            int m = (a + b) >>> 1;
            Interval i = intervals[m];
            if (x < i.begin) {
                findContainingIntervals(x, a, m, result);
            } else {
                if (x <= i.end) {
                    result.add(i);
                }
                if (maxEnd[m] >= x) {
                    findContainingIntervals(x, a, m, result);
                }
                findContainingIntervals(x, m + 1, b, result);
            }
        }
    
        java.util.Collection<Interval> findContainingIntervals(double x) {
            java.util.Collection<Interval> result  = new java.util.ArrayList<Interval>();
            findContainingIntervals(x, 0, intervals.length, result);
            return result;
        }
    
        Interval[] intervals;
        double[] maxEnd;
    
        public static void main(String[] args) {
            java.util.Random r = new java.util.Random();
            Interval[] intervals = new Interval[10000];
            for (int j = 0; j < intervals.length; j++) {
                Interval i = new Interval();
                do {
                    i.begin = r.nextDouble();
                    i.end = r.nextDouble();
                } while (i.begin >= i.end);
                intervals[j] = i;
            }
            IntervalTree it = new IntervalTree(intervals);
            double x = r.nextDouble();
            java.util.Collection<Interval> result = it.findContainingIntervals(x);
            int count = 0;
            for (Interval i : intervals) {
                if (i.begin <= x && x <= i.end) {
                    count++;
                }
            }
            System.out.println(result.size());
            System.out.println(count);
        }
    }
    

答案 3 :(得分:1)

具有O(n)复杂度的简单解决方案:

for(Range range: ranges){
  if (key >= range.start && key <= range.end)
    return range;
} 

如果我们了解有关范围的更多信息,则可以应用更聪明的算法。 他们排序了吗?它们重叠了吗?等等

答案 4 :(得分:1)

鉴于您的规格,我倾向于按尺寸订购范围,首先是最宽范围(使用自定义比较器来促进这一点)。然后只需遍历它们,并在找到包含键的范围后立即返回true。因为我们对数据一无所知,当然最宽的范围最有可能包含给定的密钥;首先搜索它们可能是(小)优化。

您可以通过其他方式预处理列表。例如,您可以排除任何完全被其他范围包围的范围。一旦遇到大于您的密钥的begin值,您就可以begin订购并提前退出。