【问题标题】:Data structure for handling intervals处理间隔的数据结构
【发布时间】:2009-12-30 20:48:36
【问题描述】:

我有一系列不能重叠的时间间隔(t_start,t_end),即:t_end(i) > t_start(i+1)。我想做以下操作:

1) 添加新的(并集)区间 [ {(1,4),(8,10)} U (3,7) = {(1,7),(8,10)} ]
2) 取出间隔 [ (1,7) - (3,5) = {(1,3),(5,7)}
3)检查一个点或一个区间是否与我的系列中的一个区间重叠(交集)
4) 在某个点 [ {(1,4),(7,8)} 之后找到第一个最小长度的“非区间”:在 4 和 7 之间有一个长度为 3 的“非区间”]。

我想知道实现这一点的好方法,复杂度低(所有操作的 log n 都可以做到)。

相关问题:Data structure for quick time interval look up

【问题讨论】:

  • 你可能会提到你打算使用什么语言,如果有的话。这可能有助于我们缩小您的搜索范围。
  • 我打算使用 C++,但我很乐意自己实现数据结构,而不依赖于 STL 或其他任何东西。所以这是一个“语言无关”的问题! :)

标签: algorithm data-structures tree intervals


【解决方案1】:

听起来您可以只使用所有边界时间的平衡二叉树

例如,将 {(1,4), (8,10), (12,15)} 表示为包含 1、4、8、10、12 和 15 的树。

每个节点都需要说明它是间隔的开始还是结束。所以:

                          8 (start)
                         /        \
                1 (start)         12 (start)
                      \             /      \
                     4 (end)   10 (end)   15 (end)

(这里所有的“结束”节点巧合地都在底部。)

那么我认为您可以在 O(log n) 时间内完成所有操作。 添加间隔

  • 查找开始时间。如果它已经在树中作为开始时间,您可以将它留在那里。如果它已经在树中作为结束时间,您将要删除它。如果它不在树中并且它没有在现有间隔内下降,您需要添加它。否则你不想添加它。

  • 找到停止时间,用同样的方法找出是否需要添加、删除,或者不需要。

  • 现在您只想添加或删除上述启动和停止节点,同时删除其间的所有现有节点。为此,您只需在树中的这两个位置或正上方重建树节点。如果树的高度是 O(log n),可以通过平衡树来保证,这需要 O(log n) 的时间。

(免责声明:如果您使用 C++ 并进行显式内存管理,则在执行此操作时最终可能会释放超过 O(log n) 块内存,但实际上释放节点所需的时间应该是我想是向添加它的人计费的。)

删除间隔大体相同。

检查一个点或区间很简单。

在给定时间后找到至少给定大小的第一个间隙也可以在 O(log n) 中完成,如果您还为每个节点缓存另外两条信息:

  • 在每个起始节点(最左边除外)中,紧靠左边的间隙大小。

  • 在每个节点中,该子树中出现的最大间隙的大小。

要查找在给定时间之后出现的第一个给定大小的间隙,首先在树中找到该时间。然后向上走,直到到达一个声称包含足够大间隙的节点。如果你从右边上来,你知道这个缺口在左边,所以你忽略它,继续往上走。否则你是从左边来的。如果该节点是起始节点,请检查其左侧的间隙是否足够大。如果是这样,你就完成了。否则,足够大的间隙必须在右边的某个地方。向右走,然后继续向下,直到找到间隙。同样,因为树的高度是 O(log n),所以遍历它 3 次(向下、向上和可能再次向下)是 O(log n)。

【讨论】:

  • 使用这种数据结构添加两个通用区间集是 O(n)。向区间集添加单个区间是 O(log n)。
  • 我还没有检查正确性,但无论如何这是一个很好的答案。这不是一个简单的算法,但如果封装得好,链接起来真的很容易。
  • @JasonOrendorff 找到一个空闲间隙是不正确的。如果你必须再往下走,你不知道往哪个方向走。解决方案是存储最大间隙左侧和最大间隙右侧。
  • @ms4py 每个节点已经存储了它所包含的最大间隙的大小。左边最大间隙的大小正好是node.left.largestGap
  • 是的,我自己也是偶然发现的 :)
【解决方案2】:

如果不知道更多细节,我建议阅读Interval Trees。区间树是更通用的kd-trees 的特殊一维案例,具有O(n log n) 构造时间和O(log n) 典型操作时间。您需要自己找到确切的算法实现,但您可以先查看CGAL

【讨论】:

    【解决方案3】:

    我知道您已经接受了一个答案,但是由于您表示您可能会在 C++ 中实现,您还可以查看 Boosts Interval Container Library (http://www.boost.org/doc/libs/1_46_1/libs/icl/doc/html/index.html)。

    【讨论】:

      【解决方案4】:

      我使用 AVL 树的区间树实现。

      public class IntervalTreeAVL<T>{
          private static class TreeNode<T>{
              private T low;
              private T high;
              private TreeNode<T> left;
              private TreeNode<T> right;
              private T max;
              private int height;
              private TreeNode(T l, T h){
                  this.low=l;
                  this.high=h;
                  this.max=high;
                  this.height=1;
              }
          }
          private TreeNode<T> root;
          public void insert(T l, T h){
              root=insert(root, l, h);
          }
          private TreeNode<T> insert(TreeNode<T> node, T l, T h){
              if(node==null){
                  return new TreeNode<T>(l, h);
              }
              else{
                  int k=((Comparable)node.low).compareTo(l);
                  if(k>0){
                      node.left=insert(node.left, l, h);
                  }
                  else{
                      node.right=insert(node.right, l, h);
                  }
                  node.height=Math.max(height(node.left), height(node.right))+1;
                  node.max=findMax(node);
                  int hd = heightDiff(node);
                  if(hd<-1){
                      int kk=heightDiff(node.right);
                      if(kk>0){
                          node.right=rightRotate(node.right);
                          return leftRotate(node);
                      }
                      else{
                          return leftRotate(node);
                      }
                  }
                  else if(hd>1){
                      if(heightDiff(node.left)<0){
                          node.left = leftRotate(node.left);
                          return rightRotate(node);
                      }
                      else{
                          return rightRotate(node);
                      } 
                  }
                  else;
              }
              return node;
          }
          private TreeNode<T> leftRotate(TreeNode<T> n){
              TreeNode<T> r =  n.right;
              n.right = r.left;
              r.left=n;
              n.height=Math.max(height(n.left), height(n.right))+1;
              r.height=Math.max(height(r.left), height(r.right))+1;
              n.max=findMax(n);
              r.max=findMax(r);
              return r;
          }
          private TreeNode<T> rightRotate(TreeNode<T> n){
              TreeNode<T> r =  n.left;
              n.left = r.right;
              r.right=n;
              n.height=Math.max(height(n.left), height(n.right))+1;
              r.height=Math.max(height(r.left), height(r.right))+1;
              n.max=findMax(n);
              r.max=findMax(r);
              return r;
          }
          private int heightDiff(TreeNode<T> a){
              if(a==null){
                  return 0;
              }
              return height(a.left)-height(a.right);
          }
          private int height(TreeNode<T> a){
              if(a==null){
                  return 0;
              }
              return a.height;
          }
          private T findMax(TreeNode<T> n){
              if(n.left==null && n.right==null){
                  return n.max;
              }
              if(n.left==null){
                  if(((Comparable)n.right.max).compareTo(n.max)>0){
                      return n.right.max;
                  }
                  else{
                      return n.max;
                  }
              }
              if(n.right==null){
                 if(((Comparable)n.left.max).compareTo(n.max)>0){
                      return n.left.max;
                  }
                  else{
                      return n.max;
                  } 
              }
              Comparable c1 = (Comparable)n.left.max;
              Comparable c2 = (Comparable)n.right.max;
              Comparable c3 = (Comparable)n.max;
              T max=null;
              if(c1.compareTo(c2)<0){
                  max=n.right.max;
              }
              else{
                  max=n.left.max;
              }
              if(c3.compareTo((Comparable)max)>0){
                  max=n.max;
              }
              return max;
          }
      
      
      TreeNode intervalSearch(T t1){
              TreeNode<T> t = root;
              while(t!=null && !isInside(t, t1)){
                  if(t.left!=null){
                          if(((Comparable)t.left.max).compareTo(t1)>0){
                          t=t.left;
                      }
                      else{
                          t=t.right;
                      }
                  }
                  else{
                      t=t.right;
                  }
              }
              return t;
          }
          private boolean isInside(TreeNode<T> node, T t){
              Comparable cLow=(Comparable)node.low;
              Comparable cHigh=(Comparable)node.high;
              int i = cLow.compareTo(t);
              int j = cHigh.compareTo(t);
              if(i<=0 && j>=0){
                  return true;
              }
              return false;
          }
      }
      

      【讨论】:

      • 这个实现不支持重叠区间的查询,对吧?看不到那个方法。
      【解决方案5】:

      我刚刚发现 Guava's RangeRangeSet 正是这样做的。

      它实现了所有引用的操作:

      1. 联合

        RangeSet<Integer> intervals = TreeRangeSet.create(); 
        intervals.add(Range.closedOpen(1,4)); // stores {[1,4)}
        intervals.add(Range.closedOpen(8,10)); // stores {[1,4), [8,10)}
        // Now unite 3,7
        intervals.add(Range.closedOpen(3,7)); // stores {[1,7), [8,10)}
        
      2. 减法

        intervals.remove(Range.closedOpen(3,5)); //stores {[1,3), [5, 7), [8, 10)}
        
      3. 十字路口

        intervals.contains(3); // returns false
        intervals.contains(5); // returns true
        intervals.encloses(Range.closedOpen(2,4)); //returns false
        intervals.subRangeSet(Range.closedOpen(2,4)); // returns {[2,3)} (isEmpty returns false)
        intervals.subRangeSet(Range.closedOpen(3,5)).isEmpty(); // returns true
        
      4. 寻找空白空间(这将与最坏情况下的集合迭代具有相同的复杂性):

        Range freeSpace(RangeSet<Integer> ranges, int size) {
            RangeSet<Integer> frees = intervals.complement().subRangeSet(Range.atLeast(0));
            for (Range free : frees.asRanges()) {
                if (!free.hasUpperBound()) {
                    return free;
                }
                if (free.upperEndpoint() - free.lowerEndpoint() >= size) {
                    return free;
                }
            }
        

      【讨论】:

      • 请注意,link-only answers are discouraged,SO 答案应该是寻找解决方案的终点(与另一个中途停留的参考相比,它往往会随着时间的推移变得陈旧)。请考虑在此处添加独立的概要,并保留链接作为参考。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-06-04
      • 1970-01-01
      • 2019-08-24
      • 2015-04-24
      • 1970-01-01
      • 1970-01-01
      • 2013-11-23
      相关资源
      最近更新 更多