排序区间查询

Posted

技术标签:

【中文标题】排序区间查询【英文标题】:Sorted intervals query 【发布时间】:2015-02-13 10:07:10 【问题描述】:

我正在寻找一种数据结构,它可以在具有以下属性的封闭区间内高效运行:

动态添加或删除间隔

为每个间隔设置并随时更改一个数字(“深度”)。没有两个深度是相同的

查找与任何给定区间重叠的所有区间,按“深度”排序

我找到的最接近的结构是Interval tree,但它以相对于它们的深度的任意顺序列出了找到的间隔。我可以收集报告的所有“未排序”间隔,然后对它们进行排序,但我希望可以避免对每个查询的结果进行排序。

请问,有没有人知道这种数据结构或有任何建议(如果可能的话)增强区间树以支持这种排序?

例子:

    将 [1,2] 添加到空结构并将其深度设置为 1 添加 [10,100],深度 = 2 添加 [5,55],深度 = 3 查询 [5,50] 报告 [10,100] 和 [5,55] 将 [10,100] 的深度设置为 3,将 [5,55] 的深度设置为 2 查询 [5,50] 报告 [5,55] 和 [10,100]

编辑

我对快速添加/删除和查询更感兴趣,而不是更新深度。如果这有助于加速其他操作,则深度可能需要 O(n)。

【问题讨论】:

a treap 呢? @Rerito 我知道treap,怎么样?它的节点必须具有随机优先级,“深度”不必是随机的(并且可以随时更改),如果这就是您的意思..? 不能有两个深度相同的区间。任何时候,对吧?在这种情况下,带有 hashmap 的二叉搜索树方法(使用增强树)非常适合您 @Rerito 正确,所有深度都是不同的。但是我不明白那个哈希图,它与存储深度和间隔有什么不同?我的意思是,即使使用 hashmap,我也必须在之后对结果进行排序,对吗? 正确,但深度的更新会少很多痛苦 【参考方案1】:

让我们假设您想要的算法存在。然后让我们创建一组一百万个区间,每个区间为[1, 1],具有随机深度,并将它们插入到这样的区间树中。然后我们查询区间[1, 1]。它应该按排序顺序返回所有区间,复杂度为O(M + log N),但复杂度为N = 1,因此我们以线性时间对一组M 元素进行排序。

换句话说,从区间树中获取元素后按深度对元素进行排序在复杂性方面与理论上可能的结果一样好。

【讨论】:

这就是为什么我正在寻找一种数据结构,它会以某种方式保持间隔排序并仍然允许快速查询,例如。 我的意思是,如果存在这样的数据结构,您将能够使用该数据结构在线性时间内对数组进行排序。因此,理论上这种数据结构是不存在的。 你好伊沙梅尔!由于您的回答获得了最多的选票,因此很可能会自动将赏金奖励给您。我不想听起来忘恩负义,但我想我欠你一个解释,为什么我不会接受它作为答案。我相信您提供的论点是不正确的 - 我不会尝试在某处填充 N 个随机间隔并期望它们在第一次查询时被排序。事实上,这与我所追求的完全相反。我想要一个在元素来来去去时自动保持其顺序的结构。举个例子: 我不会尝试构建一个随机数向量,一旦查询就会排序,当然这是 O(N log N)。但是我可以很容易地在已经排序的向量中添加一个数字,并在 O(N) 中保持排序。甚至可以在 O(log N) 中添加一个数字,并且仍然能够在 O(N) 中以正确的顺序报告数字。我想说的是,您的论点似乎基于众所周知的lower bound,而我的问题是关于在构建、维护和查询数据结构之间分散工作。 假设区间树的长度为N,查询返回的元素个数为M。您当前的方法具有O(log N) 插入/删除复杂性和O(K log K + log N) 查询复杂性。你想改进哪一项,你愿意牺牲哪一项,牺牲多少?【参考方案2】:

你设置的深度,相当于它们假想的list中的间隔位置。因此,通常的数字对列表就足够了。 List 可以轻松添加、删除或切换其项目。

如果您还需要找到给定间隔的深度,请为它创建一个函数(但您没有提到需要)

【讨论】:

虽然您对列表中的位置是正确的,但问题是查询是 O(n)。这就是我所说的“有效”的意思,因为区间树显然做得更好,但话又说回来,表示深度存在问题...... @EcirHana 您想“避免对每个查询的结果进行排序”。我就是这么回答的。 O(n) 比 n*Log(n) 好得多。如果您看到其他问题,请提出另一个问题....不可能优化所有内容。你必须: 1. 写下所有可能的功能。 2. 定义功能速度的优先级。 你在错误地引用我的话。整个引文都谈到了从间隔树中收集结果,即 O(log n + m),首先我想避免排序。但更重要的是,我正在寻找有效的数据结构,以最幼稚的方式迭代列表没有任何效率。无需提出其他问题或使用大写字母... @EcirHana 1. 你还没有定义函数优化的优先级。因此,您的问题未定义。你应该事先说你需要什么,而不是事后说:“哦,不,这不是我需要的”。 2. 大写的单个词仅表示逻辑重音。使用它是正常的。我只是试图使文本更易于理解。但如你所愿......【参考方案3】:

这是我在 Java 中使用TreeMap 的解决方案,基本上是Binary-Tree

测试:http://ideone.com/f0OlHI

复杂性

     Insert : 2 * O(log n)

     Remove : 2 * O(log n)

     Search : 1 * O(log n)

ChangeDepth : 7 * O(log n)

findOverlap : O(n)

IntervalDataSet.java

class IntervalDataSet

    private TreeMap<Integer,Interval> map;

    public IntervalDataSet ()
    
        map = new TreeMap<Integer,Interval> ();
    

    public void print ()
    
        for(Map.Entry<Integer,Interval> entry : map.entrySet())
        
          Integer key = entry.getKey();
          Interval value = entry.getValue();

          System.out.println(key+" => ["+value.min+","+value.max+"] ");
        
    

    public boolean changeDepth (int depth, int newDepth)
    
        if (!map.containsKey(depth)) return false;

        if (map.containsKey(newDepth)) return false;

        Interval in = map.get(depth);

        in.depth = newDepth;

        remove(depth); insert(in);

        return true;
    

    public boolean insert (Interval in)
    
        if (in == null) return false;

        if (map.containsKey(in.depth)) return false;

        map.put(in.depth, in); return true;
    

    public boolean remove (int depth)
    
        if (!map.containsKey(depth)) return false;

        map.remove(depth); return true;
    

    public Interval get (int depth)
    
        return map.get(depth);
    

    public void print (int depth)
    
        if (!map.containsKey(depth))
          System.out.println(depth+" => X ");
        else
          map.get(depth).print();
    

    public void printOverlappingIntervals (Interval in)
    
        for (Interval interval : map.values())
            if (interval.intersect(in))
                interval.print();
    

    public ArrayList<Interval> getOverlappingIntervals (Interval in)
    
        ArrayList<Interval> list = new ArrayList<Interval>();

        for (Interval interval : map.values())
            if (interval.intersect(in))
                list.add(interval);

        return list;
    

    public int size ()
    
        return map.size();
    


Interval.java

class Interval

    public int min;
    public int max;
    public int depth;

    public Interval (int min, int max, int depth)
    
        this.min = min;
        this.max = max;
        this.depth = depth;
    

    public boolean intersect (Interval b)
    
        return (b != null
            && ((this.min >= b.min && this.min <= b.max)
                || (this.max >= b.min && this.max <= b.max))
            );
    

    public void print ()
    
        System.out.println(depth+" => ["+min+","+max+"] ");
    

Test.java

class Test

  public static void main(String[] args) 
  
    System.out.println("Test Start!");
    System.out.println("--------------");

    IntervalDataSet data = new IntervalDataSet ();

    data.insert(new Interval( 1,3, 0 ));
    data.insert(new Interval( 2,4, 1 ));
    data.insert(new Interval( 3,5, 3 ));
    data.insert(new Interval( 4,6, 4 ));
    System.out.println("initial values");
    data.print();

    System.out.println("--------------");
    System.out.println("Intervals overlapping [2,3]");

    data.printOverlappingIntervals(new Interval( 2,3, -1 ));
    System.out.println("--------------");

    System.out.println("change depth 0 to 2");
    data.changeDepth( 0, 2 );
    data.print();
    System.out.println("--------------");

    System.out.println("remove depth 4");
    data.remove( 4 );
    data.print();
    System.out.println("--------------");

    System.out.println("change depth 1 to 4");
    data.changeDepth( 1, 4 );
    data.print();
    System.out.println("--------------");

    System.out.println("Test End!");
  


IntervalDataSet2

复杂性

initialization : O(n)

   findOverlap : 2 * O(log n) + T(merge)

class IntervalDataSet2

    private Integer [] key;
    private TreeMap<Integer,Interval> [] val;
    private int min, max, size;

    public IntervalDataSet2 (Collection<Interval> init)
    
        TreeMap<Integer,TreeMap<Integer,Interval>> map
            = new TreeSet<Integer,TreeMap<Integer,Interval>> ();

        for (Interval in : init)
        
            if (!map.containsKey(in.min))
                map.put(in.min,
                    new TreeMap<Integer,Interval> ());

            map.get(in.min).put(in.depth,in);

            if (!map.containsKey(in.max))
                map.put(in.max,
                    new TreeMap<Integer,Interval> ());

            map.get(in.max).put(in.depth,in);
        

        key = new Integer [map.size()];
        val = new TreeMap<Integer,Interval> [map.size()];

        int i = 0;
        for (Integer value : map.keySet())
        
            key [i] = value;
            val [i] = map.get(value);
            i++ ;
        

        this.size = map.size();
        this.min = key [0];
        this.max = key [size-1];

    

    private int binarySearch (int value, int a, int b)
    
        if (a == b)
            return a;

        if (key[(a+b)/2] == value)
            return ((a+b)/2);

        if (key[(a+b)/2] < value)
            return binarySearch(value, ((a+b)/2)+1, b);
        else
            return binarySearch(value, (a, (a+b)/2)-1);
    

    public TreeMap<Integer,Interval> findOverlap (Interval in)
    
        TreeMap<Integer,Interval> solution
            = new TreeMap<Integer,Interval> ();

        int alpha = in.min;
        int beta = in.max;

        if (alpha > this.max || beta < this.min)
            return solution;

        int i = binarySearch(alpha, 0,(size-1));
        int j = binarySearch(beta, 0,(size-1));

        while (alpha <= beta && key[i] < alpha) i++;
        while  (alpha <= beta && key[j] > beta) j--;

        for (int k = i; k <= j; k++)
            solution.addAll ( val[k] );

        return solution;
    


【讨论】:

如何查询IntervalDataSet? @EcirHana 更新代码,添加测试用例,查看链接查看执行 非常感谢!也许我遗漏了一些明显的东西,但我的问题是,如何查询“间隔”?也就是说,我不想报告给定深度的一个区间。棘手的部分是报告给定范围重叠的所有间隔,并按深度排序报告它们。我无法理解您的代码在哪里执行此操作? @EcirHana 添加功能getOverlappingIntervals & printOverlappingIntervals,查看测试链接 因此您正在测试树的全部内容以查看哪些区间与查询重叠。在什么方面它会比一个规则的间隔树更好,然后是一个排序步骤? (根据 OP 的想法)【参考方案4】:

到头来想想这个问题还是挺难的。你所拥有的实际上是一个一维空间,实际上是一条线。通过添加深度,您可以获得第二个坐标。通过要求深度是唯一的,您可以实际绘制图片。

您的查询是将图片的每个间隔(线)与从 x1 到 x2 的矩形以及 Y 中的每个 y 相交。

所以天真的看这个问题就是按照y的顺序比较每条线是否相交。由于您想要所有结果,因此需要 O(n) 才能找到答案。而且这个答案也必须按 O(m log m) 中的深度排序。

您尝试使用一维 R 树。允许您在旅途中定义区域。在这样的结构中,每个节点都跨越一个从 min 到 max 的区域。您现在可以进一步拆分该区域。由于拆分实际上是拆分它,因此两个部分中都有适合的间隔,因此它们存储在节点内(而不是子节点内)。在这些子节点中,您会再次获得这样的列表,依此类推。

对于您的搜索,您检查所有节点和子节点,哪些区域与您的搜索间隔相交。

在每个节点内,间隔列表根据它们的深度值排序。

因此问题被简化为许多排序列表(可能包含在您的搜索区间内的所有区间)。现在必须针对与您的搜索间隔真正相交的每个间隔过滤这些列表。但是您不需要全部过滤。因为如果这样一个节点区间完全包含在您的搜索区间内,那么它的所有区间及其所有子区间都与您的搜索区间真正相交(因为它们完全包含在其中)。

要组合所有这些列表,您只需使用联合组合,您可以选择该联合中具有最小深度的下一个元素。您按其第一个元素的深度(每个列表中深度最小的元素)对所有这些列表进行排序。现在您查找第一个元素并将其移动到结果中。您现在将列表中的下一个元素与下一个列表的第一个元素进行比较,如果深度仍然更小,则将其复制到。如果深度变大,您只需使用正确的位置对它进行排序,取 log k(其中 k 是您的集合中非空列表的数量),然后继续现在的第一个列表并重复它,直到所有列表都为空或完成因为您维护每个列表的光标位置。

这样您只需对列表进行排序,如果它仍然更小,则将其与下一个元素进行比较或插入它。

这是我能想到的最好的结构。首先,您可以轻松排除几乎所有不能与之相交的区间。比你根据潜力列表组成结果,你知道列表是完整结果的一部分(有人可以争辩说,只有一定数量的间隔列表必须检查,因为树分裂得非常快)。通过控制拆分策略,您可以控制每个部分的成本。例如,如果您仅以 >10 个间隔拆分,您将确保 k

最坏的情况很糟糕,但我猜预期的实际性能会很好,而且会好得多,因为您使用了已经排序的潜在交叉点的不同列表。

请记住,如果一个节点有子元素,它本身包含一个与分割点相交的所有区间的列表。因此,如果您的搜索区间也与分割点相交,则该节点的每个区间也是您的结果的一部分。

所以请随意尝试这种结构。一天之内应该很容易实现。

【讨论】:

以上是关于排序区间查询的主要内容,如果未能解决你的问题,请参考以下文章

查询区间内距离标尺最近且不大于最大值的元素

[CQOI2015]任务查询系统

分块之区间加法和询问小于指定元素的个数

模拟11题解

4923: [Lydsy1706月赛]K小值查询 平衡树 非旋转Treap

线段树(单点修改+区间查询)&(区间修改+区间查询)