假设我有一个未排序的重叠ranges
数组。每个range
只是一对整数begin
和end
。现在我想查找给定的key
是否属于ranges
中的至少一个。也许,我必须知道它所属的ranges
。
我们可以假设ranges
数组需要大约1M并且适合内存。我正在寻找一种简单的算法,它只使用标准的JDK集合,没有任何3d派对库和特殊的数据结构,但工作速度相当快。
你会建议什么?
答案 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)
如果您不需要知道哪个区间包含您的观点(编辑:我猜您可能会这样做,但我会留下这个答案给其他有这个问题的人没有),然后
通过计算两个数组B和E来预处理间隔.B是按排序顺序的begin值。 E是按排序顺序结束的值。
为了查询点x,使用二分搜索来找到最小索引i,使得B [i]> x和最小索引j使得E [j]≥x。包含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);
}
}
答案 3 :(得分:1)
具有O(n)复杂度的简单解决方案:
for(Range range: ranges){
if (key >= range.start && key <= range.end)
return range;
}
如果我们了解有关范围的更多信息,则可以应用更聪明的算法。 他们排序了吗?它们重叠了吗?等等
答案 4 :(得分:1)
鉴于您的规格,我倾向于按尺寸订购范围,首先是最宽范围(使用自定义比较器来促进这一点)。然后只需遍历它们,并在找到包含键的范围后立即返回true。因为我们对数据一无所知,当然最宽的范围最有可能包含给定的密钥;首先搜索它们可能是(小)优化。
您可以通过其他方式预处理列表。例如,您可以排除任何完全被其他范围包围的范围。一旦遇到大于您的密钥的begin
值,您就可以begin
订购并提前退出。