【问题标题】:Range lookup in JavaJava中的范围查找
【发布时间】:2012-01-01 08:30:02
【问题描述】:

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

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

你有什么建议?

【问题讨论】:

  • 范围是排序的,还是完全不受约束的?
  • 我假设线性搜索不会削减它?可能有非常聪明的方法可以做到这一点,但它们可能会违反您的其他要求。我们有多少范围和键的任何迹象?
  • 我不清楚这个问题,但听起来你需要一个 {key, range} 对的哈希表。
  • 你的问题有点混乱。对于属于范围的键,它必须在开始和结束之间吗?你想找到键所属的每个范围吗? “范围可能需要〜1M”。您的意思是范围的集合将是大约 1M 不同的范围?
  • @ben:问题似乎更像是首先如何为每个key 找到range。找到后当然可以使用哈希表来存储它,但我不明白如果不先解决 OP 的问题,如何构造这样的哈希表。

标签: java algorithm search data-structures


【解决方案1】:

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

用于搜索的Comparator compare(x,y) 应该返回

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

正如@Per 所说,您需要一个不同的、更严格的Comparator 进行排序,但前两个子句仍然成立。

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

这实际上是一个静态interval tree,即没有 O(lg N) 插入或删除,就像排序数组可以被视为静态二叉搜索树一样。

【讨论】:

  • 听起来不错!如何建议对ranges 进行排序?通过beginend
  • 您的Comparator 到底是做什么的?我怀疑这种方法是否适用于重叠区间——标准区间树有两个排序列表,用于与每个分割点重叠的区间,并且 CLRS 中描述的数据结构需要扩充树(按左端点排序) 通过每个子树中的最大右端点。
  • @Michael:扩大答案。
  • Comparator 的选择具有不可传递的equals,这违反了Comparator 契约。即使sort 以某种方式设法返回与您定义的非自反传递关系一致的数组,二分查找也不起作用。我们可以有一个带有间隔的排序数组…, [2, 4], [0, 3], … 然后通过比较 [1, 1] 和 [2, 4] 开始的二进制搜索将继续到错误的一半。
  • 哦,equals 方法并没有像我想象的那样做。澄清一下:compare 函数是不可传递的,因为compare(x,y) == 0 && compare(y,z) == 0 并不暗示compare(x,z) == 0
【解决方案2】:

我相信这就是你要找的东西:http://en.wikipedia.org/wiki/Interval_tree

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

【讨论】:

    【解决方案3】:

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

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

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

    【讨论】:

      【解决方案4】:

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

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

      【讨论】:

        【解决方案5】:

        如果您不需要知道 哪个 区间包含您的观点(编辑:我想您可能知道,但我会将这个答案留给其他有这个问题的人),那么

        1. 通过计算两个数组 B 和 E 来预处理区间。B 是按排序顺序排列的 begin 值。 E 是排序后的 end 值。

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


        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);
            }
        }
        

        【讨论】:

        • 太棒了!如果我想知道哪些区间包含该点怎么办?
        • @Michael 将 CLRS 中的算法(如区间树的维基百科页面中所述)转换为使用数组而不是二叉树。我现在得走了,但如果没有其他人先做,我会在一段时间内发布详细信息。
        • @Michael 添加了 Java 代码。如果 StackOverflow 还没有为 Aiur 声明它,请考虑它是 WTFPL 许可的。 maxEnd[m]包含intervals[a], ..., intervals[m - 1]中end的最大值。
        • 感谢您提及区间树上的维基百科页面!
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-10-17
        相关资源
        最近更新 更多