用于快速检查重叠范围的数字的数据结构

时间:2014-04-12 05:02:58

标签: algorithm data-structures

我们说我有以下数字范围:

0-500 0-100 75-127 125-157 130-198 198-200

现在,让我们说我需要能够检查任何给定的数字并查看它所在的范围。最有效地使用哪种数据结构来告诉数字100属于范围0-500,0-100和75-127?我只想要一个包含起始值的二叉树吗?在这种情况下,树中的每个节点是否可以在该起点处保存包含每个范围的多个对象?

请注意,我只需要检索此特定应用程序,我真的不需要在进程中修改它,因此检索速度是我的首要任务。

谢谢!

4 个答案:

答案 0 :(得分:4)

您需要的是interval tree。区间树是非常通用的概念,并且对于每个问题在节点中保持略微不同的数据。在您的情况下,每个节点将保留一个输入间隔列表,覆盖节点所代表的间隔。

答案 1 :(得分:2)

R表示可能的范围数。 (对于您的示例R=6

创建R哈希表,使每个哈希表只能包含一个范围。对于您的示例,您需要创建6个哈希表。第一个哈希表R1将仅包含0-500的值。

填充哈希表。

每个号码都会进入适当的哈希表。对于您的示例,我需要100R1R2。如果R3很大,那么您需要创建大量哈希表。但是总空间受存储在所有哈希表中的实际数据的限制。

Retrival:

对于任何给定的数字,检查每个R哈希表中是否存在该数字。您可以通过选择要查看的哈希表来进一步优化。例如,对于R,您只需要查看6个哈希表中的3个。

时间复杂度:

在哈希表中搜索单个值需要100(平均)。所以 分摊constant time以查看哈希表中是否存在数字。

分摊O(1)以产生输出,因为我们需要查看所有哈希表以产生输出

答案 2 :(得分:1)

<强>编辑:

  • 快得多
  • 内存消耗更高效
  • 对于1,000输入,保证在最坏情况下它的工作时间不到100毫秒
  • 对于10 000个输入范围, 10x 超过所需输入,对于from ex {0 - #} x50,from的50个重复,它仍然可以在不到100毫秒的时间内工作1 - #} x50等...适用于所有0 - 100 0 - 500 50 - 500 20 - 300 范围。

<强>目标

给定10,000个输入范围间隔(从到 - 为了使场景最差,每个'从'重复50次)。程序获取一个目标值并显示目标所属的所有范围。

关于它如何适用于4个范围的示例:

考虑以下范围:

20 - 300
0 - 500
0 - 100

目标:40

<强>输出

from

算法说明(使用前面的例子):

每个index++值都从index = 0开始映射到From => index 0 => 0 50 => 1 20 => 2 。所以,在我们的例子中:

pointers

现在有一个名为i的树集数组,此数组的每个索引key都引用树集中值i的{​​{1}}。所以pointers[0]指的是'from':0,pointers[1]指的是'from':50,pointers[2]指的是'from':20。

现在,我们分别通过查看树地图tofrom值添加到每个'key' => 'value'值,其中valuekey的索引pointers数组。

此外,我们希望在每个索引的to数组中添加的pointers值按降序排序(稍后我会解释原因)。

所以现在指针变成这样:

index => TreeSet[values..]
0 => 500 | 100
1 => 500
2 => 300

现在我们已准备好获得目标所属的范围。

target = 40

1 - 在树形图中搜索最接近的楼层键40。该程序发现20是最接近的一个。

2 - 它指向指针数组中对应于20的索引。要获得20的索引:查看关键字20处的树形图.20的索引是2。

3 - 现在转到pointers[2],它发现有数字300。

4 - 现在应用程序检查300是否小于目标,这是因为我之前提到过,所创建的每个树集都按降序排序。因此,如果300小于目标,则无需继续检查pointers[2]中的下一个值,因为它可以保证它们更小。

5 - 在这种情况下,300大于目标,然后将密钥打印为from,将pointer[2] {current element}打印为to

6 - 由于在pointers[2]中只找到一个元素,for循环退出,并且密钥从树形图中删除,因此下次当程序想要找到下一个最近的楼层密钥时,它会找到下一个,如果它存在。

7 - (while循环的下一次迭代)删除键20后找到的下一个键是0,根据树映射索引为0。

8 - 转到pointers[0]。它发现pointers[0]中树集中的元素数量为2。

9 - 从第一个元素pointers[0] {first element}开始。 500不到40?不,打印“范围”。下一个元素是100不到40?不,打印“范围”。没有更多元素退出循环。

10 - 从树形图中删除键0。

11 - 现在,While循环条件检查目标是否存在最接近的楼层键。不,因为0和20被删除了。所以条件不满意,退出循环。

12 - 消耗的打印时间和结束程序:)

希望您能找到有用的解释。

<强>代码

import java.util.Collections;
import java.util.TreeMap;
import java.util.TreeSet;

public class Interval {
    public static void main(String[] args) {
        int MAX_SIZE = 10000;
        int[] from = new int[MAX_SIZE];
        int[] to = new int[MAX_SIZE];

        //Generate 10,000 (from - to), with 50 redundant (from range) for every value of from. (to make the application heavy)
        int c = 0;
        for(int i=0; i<MAX_SIZE;i++){
            from[i] = 0+c;
            to[i] = from[i] + (int)(Math.random()*100);
            if(i%50 == 0) 
                c++;
        }

        //Start time counting
        long time = System.currentTimeMillis();

        int target = 97;
        TreeMap<Integer, Integer> treePointer = new TreeMap<Integer, Integer>(); // will sotre <From, index++>

        int index = 0;
        int size = from.length;
        TreeSet<Integer>[] pointers = new TreeSet[size]; //Array of tree set to store values of every "from" range

        //insert into tree
        for(int i=0; i<from.length;i++){
            if(!treePointer.containsKey(from[i])){ //if the "from" does not exist in the tree yet, insert it
                treePointer.put(from[i], index);
                pointers[index++] = new TreeSet<Integer>(Collections.reverseOrder()); //sort descending order
            }

            //index of "from" in the pointers array
            int virtualIndex = treePointer.get(from[i]);
            //add the 'to' element to the corresponding index of "from" in Tree Set at pointers[index of "from"]
            pointers[virtualIndex].add(to[i]);
        }

        // Display part of the pointers array to understand how elements are stored
//      for(int i=0; i<10; i++){
//          for(int current : pointers[i]){
//              System.out.print(current + " ");
//          }
//          System.out.println();
//      }

        //Start checking for the ranges
        Integer currentKey = -1; //dummy number at first
        while((currentKey = treePointer.floorKey(target)) != null){ // while there is a closest floor key
          //get index assigned to the closest floor key
            int virtualIndex = treePointer.get(currentKey);
            //loop on the elements found at pointers[index of closest floor number]
            for(int toValue : pointers[virtualIndex]){
                if(toValue < target) //remember the values are sorted in a descending order, so whenever the value becomes smaller than the target don't continue the for loop
                    break;
                System.out.println(currentKey + " - " + toValue); // else target less or equal to toValue, so print range
            }
            treePointer.remove(currentKey); //remove key from tree to fetch the next floor key in the next while iteration
        }
        //Display time consumed in ms
        System.out.println("\nTotal time: " + (System.currentTimeMillis() - time) + " ms");
    }
}

答案 3 :(得分:1)

假设你有足够的内存,我会使用一个列表数组,其中列表是包含索引的所有范围。

这是一个Python解决方案,更详细地显示了算法:

# (Inclusive) ranges
ranges = [(0,500), (0,100), (75,127), (125,157), (130,198), (198,200)]
smallest = min(r[0] for r in ranges)
largest  = max(r[1] for r in ranges)

# Ceate table
table = [[] for i in range(smallest, largest+1)] # List of lists
for r in ranges: # pre-compute results
    mn, mx = r
    for index in range(mn, mx+1):
        table[index - smallest].append(r)

def check(n):
    'Return list of ranges containing n'
    if smallest <= n <= largest:
        return table[n - smallest]
    else:
        return []   # Out of range

for n in [-10, 10, 75, 127, 129, 130, 158, 197, 198, 199, 500, 501]:
    print('%3i is in groups: %r' % (n, check(n)))

输出结果为:

-10 is in groups: []
 10 is in groups: [(0, 500), (0, 100)]
 75 is in groups: [(0, 500), (0, 100), (75, 127)]
127 is in groups: [(0, 500), (75, 127), (125, 157)]
129 is in groups: [(0, 500), (125, 157)]
130 is in groups: [(0, 500), (125, 157), (130, 198)]
158 is in groups: [(0, 500), (130, 198)]
197 is in groups: [(0, 500), (130, 198)]
198 is in groups: [(0, 500), (130, 198), (198, 200)]
199 is in groups: [(0, 500), (198, 200)]
500 is in groups: [(0, 500)]
501 is in groups: []

进一步的优化,例如使用位集来存储每个索引而不是列表的范围是可能的。

从下面的评论中,我决定修改上面的内容,在上面的表查找方法和基于直接比较的更慢但更大的内存效率解决方案之间进行选择。 (理想情况下,还会包含基于区间树的解决方案):

# (Inclusive) ranges
ranges = [(0,500), (0,100), (75,127), (125,157), (130,198), (198,200)]
limit = 1000000 # Or whatever

smallest = min(r[0] for r in ranges)
largest  = max(r[1] for r in ranges)

if (largest - smallest) * len(ranges) < limit:
    # Ceate table
    table = [[] for i in range(smallest, largest+1)] # List of lists
    for r in ranges:
        mn, mx = r
        for index in range(mn, mx+1):
            table[index - smallest].append(r)

    def check(n):
        'Return list of ranges containing n'
        if smallest <= n <= largest:
            return table[n - smallest]
        else:
            return []   # Out of range
else:
    # mpre emory efficient method, for example
    def check(n):
        return [(mn, mx) for mn, mx in ranges if mn <= n <= mx]

for n in [-10, 10, 75, 127, 129, 130, 158, 197, 198, 199, 500, 501]:
    print('%3i is in groups: %r' % (n, check(n)))