【问题标题】:Find the smallest area rectangle covering a query point找到覆盖查询点的最小区域矩形
【发布时间】:2017-03-30 19:52:56
【问题描述】:

我正在做一个与计算几何有关的个人项目。标题中的问题是对我正在尝试但正在努力有效解决的小子问题之一的抽象。希望它足够通用,可能不仅仅对我有用!


问题

假设我们在平面上有一组 S 矩形,所有这些矩形的边都平行于坐标轴(没有旋转)。对于我的问题,我们假设矩形交叉点非常常见。但它们也非常好:如果两个矩形相交,我们可以假设其中一个总是完全包含另一个。所以没有“部分”重叠。

我想以如下方式存储这些矩形:

  • 我们可以高效地添加新矩形。
  • 给定一个查询点 (x,y),我们可以有效地报告 包含该点的最小区域的矩形。

插图为后者提供了动机。我们总是希望找到包含查询点的嵌套最深的矩形,因此它始终是最小区域之一。


我的想法

所以我知道 R-Tree 和 Quad-Tree 都经常用于解决空间索引问题,而且确实在某些情况下两者都可以很好地工作。 R-Trees 的问题是它们在最坏的情况下会降级为线性性能。

我考虑过构建一组基于嵌套的平衡二叉树。节点 r 的左子树包含矩形 r 内的所有矩形。右子树包含 r 所在的所有矩形。图示的示例将具有三棵树。

但是如果没有一个矩形是嵌套的呢?然后你需要 1 个元素的 O(n) 树,而且我们有一些性能与通过框的线性扫描一样差的东西。


在最坏的情况下,我们如何以渐近亚线性时间的方式解决这个问题?即使这意味着在最佳情况下牺牲一些性能或存储要求。 (我假设对于这样的问题,可能需要维护两个数据结构,这很酷)

我确信允许矩形相交的非常具体的方式应该有助于解决这个问题。事实上,对我来说,它看起来像是对数性能的候选者,但我只是没有得到任何结果。

提前感谢您提出的任何想法!

【问题讨论】:

    标签: algorithm geometry quadtree spatial-query r-tree


    【解决方案1】:

    我建议存储每个嵌套级别的矩形,并处理每个级别的矩形查找。找到点所在的顶级矩形后,您可以查看该矩形内的二级矩形,使用相同的方法找到该点所在的矩形,然后查看第三级, 等等。

    为了避免在 O(n) 的最坏情况下找到矩形,您可以使用一种三元空间树,在其中重复绘制一条穿过空间的垂直线并将矩形分成三组:左边(蓝色),与线相交的(红色)和右边(绿色)的线。对于相交的矩形组(或者一旦垂直线与大部分或所有矩形相交),您切换到水平线并将矩形分成在线上方、相交和下方的组。

    然后您将反复检查该点是在线的左/右还是上方/下方,然后继续检查同一侧的矩形以及与该线相交的矩形。

    在示例中,实际上只需要检查四个矩形来找出哪个矩形包含该点。


    如果我们对示例中的矩形使用以下编号:

    那么三元空间树应该是这样的:

    【讨论】:

    • 如果所有矩形都覆盖整个区域,这将如何避免 O(n) 最坏情况?我看不到您的方法的最坏情况保证。您的示例假设它们不重叠,但是 R 树已经可以很好地工作了。
    • @Anony-Mousse 我不是假设矩形没有嵌套,我只是建议按级别存储和搜索它们。但是你说得对,我对三叉树的建议只会提高每层的搜索量,如果查询点在每个矩形中,它们都必须考虑,所以仍然是 O(n)。
    【解决方案2】:

    您可以沿着矩形的边缘从 xMin 到 xMax 和 yMin 到 yMax 划分区域。这最多给出 (2n - 1)^2 个字段。每个字段要么完全为空,要么被可见的(部分)单个矩形占据。现在您可以轻松地创建一个带有顶部矩形链接的树结构(例如,计算 x 和 y 方向上的分区数,其中中间有更多的划分并创建一个节点......递归地进行)。因此查找将采用 O(log n^2) 次线性。并且数据结构占用O(n^2)空间。

    在复杂度方面这是一个更好的实现,因为索引的搜索可以分开搜索顶部的矩形无论矩形的配置如何,都只有O(log n)并且公平易于实现:

    private int[] x, y;
    private Rectangle[][] r;
    
    public RectangleFinder(Rectangle[] rectangles) {
        Set<Integer> xPartition = new HashSet<>(), yPartition = new HashSet<>();
        for (int i = 0; i < rectangles.length; i++) {
            xPartition.add(rectangles[i].getX());
            yPartition.add(rectangles[i].getY());
            xPartition.add(rectangles[i].getX() + rectangles[i].getWidth());
            yPartition.add(rectangles[i].getY() + rectangles[i].getHeight());
        }
        x = new int[xPartition.size()];
        y = new int[yPartition.size()];
        r = new Rectangle[x.length][y.length];
        int c = 0;
        for (Iterator<Integer> itr = xPartition.iterator(); itr.hasNext();)
            x[c++] = itr.next();
        c = 0;
        for (Iterator<Integer> itr = yPartition.iterator(); itr.hasNext();)
            y[c++] = itr.next();
        Arrays.sort(x);
        Arrays.sort(y);
        for (int i = 0; i < x.length; i++)
            for (int j = 0; j < y.length; j++)
                r[i][j] = rectangleOnTop(x[i], y[j]);
    }
    
    public Rectangle find(int x, int y) {
        return r[getIndex(x, this.x, 0, this.x.length)][getIndex(y, this.y, 0, this.y.length)];
    }
    
    private int getIndex(int n, int[] arr, int start, int len) {
        if (len <= 1)
            return start;
        int mid = start + len / 2;
        if (n < arr[mid])
            return getIndex(n, arr, start, len / 2);
        else
            return getIndex(n, arr, mid, len - len / 2);
    }
    

    【讨论】:

      【解决方案3】:

      几乎任何索引都可以降级到最坏情况 O(n)。

      问题是您是否会拥有此类有害数据,以及您是针对最坏情况还是针对真实数据进行优化。

      考虑 n 个相同大小、重叠的矩形,以及交叉点中的一个点……您不会有太多优化的机会。

      R 树是一个相当不错的选择。您可以进行优先搜索,并选择较小的矩形。

      但是您的草图表明您的矩形通常是嵌套的,而不是重叠的。标准的 R-tree 不能很好地处理这个问题。相反,您可能需要修改 R 树以使用正是这种结构,并仅将嵌套的矩形存储为父级的一部分。

      【讨论】:

      • 没有部分重叠的矩形,如问题中所述。但是,矩形可能都在彼此内部,并且查询点都在它们内部,所以你的点仍然存在。
      • 如果矩形都在彼此的内部,那么实际上很容易得到 O(log n)。检查该点是否在中间深度的矩形内,如果是,则该点只能在这个或更高的矩形中,如果不是,它必须在更深的矩形中,递归重复。
      【解决方案4】:

      PH-Tree 怎么样? PH-Tree 本质上是一个形状像四叉树的四叉树,但具有一些可能非常适合您的情况的独特属性,例如非常有效的更新和小矩形局部性的高可能性。

      基础知识:

      • PH-Tree 是位级别的尝试,这意味着它在每个位位置的所有维度上进行拆分。这意味着,对于 64 位浮点数据,树的最大深度为 64。
      • 树是隐式 z 排序的
      • 查询速度通常可与 R*Tree 或 STR-Tree 相媲美,对于您的情况,它可能要快得多,见下文。
      • 插入/删除速度等于或优于 STR-Trees,并且优于我所知道的任何其他 R-Tree 类型。
      • 树的形状仅由数据决定,而不是由插入顺序决定。这意味着永远不会有任何代价高昂的再平衡。事实上,树保证任何插入或删除都不会影响两个以上的节点(具有子/父关系)。

      存储矩形:PH-Tree 只能存储数据向量,即。点。为了存储(轴对齐)矩形,默认情况下它采用“左下角”和“右上角”,但这些角为单个向量。例如,一个 2D 矩形 (2,2)-(4,5) 被存储为一个 4-dim 向量 (2,2,4,5)。可能不是很明显,但是这种表示仍然允许高效的查询,例如窗口查询和最近邻查询,见一些结果here 和一些解释here。

      树不能直接存储同一个矩形两次。相反,您会将计数器与每个“键”相关联。对于具有“n”个相同矩形的特殊情况,这实际上具有生成的树将只包含一个键的优点,因此可以在几乎恒定的时间内确定与最小矩形的重叠。

      查询性能:从性能结果可以看出,PH-Tree(取决于数据集)在返回很少结果的小查询窗口(@98​​7654324@,图 16)时速度最快。我不确定性能优势是否与小查询窗口大小或小结果大小有关。但是如果它连接到第一个,那么您的查询应该非常快,因为本质上您的查询窗口是一个点。

      针对小矩形尺寸进行优化:由于将矩形编码为单个向量,因此最小的矩形很可能(保证??)位于同样包含您的搜索点的叶节点中。 通常,查询是按 z 顺序遍历的,因此要利用小矩形的局部性,您需要编写一个特殊的查询。这应该不难,我想我可以简单地使用 PH-Tree k-nearest-neighbor 实现并提供自定义距离函数。当前的 kNN 以找到具有搜索点的节点开始,然后扩展搜索区域,直到找到所有最近的邻居。我确实相信使用自定义距离函数就足够了,但您可能需要做一些研究来证明这一点。

      PH-Tree 的完整代码 (Java) 可在上面的链接中找到。为了比较,您可能想查看我的其他索引实现here(R*Tree、四叉树、STR-Tree)。

      【讨论】:

      • 是的,就是我。不久前我注意到树中矩形的位置部分取决于它们的大小。例如,靠近空间对角线 (0,0,0...)/(MAX,MAX,MAX,...) 的节点都很小。反过来只是部分正确,与对角线距离最大的矩形可以小或大,具体取决于象限。我从来没有时间进一步研究这个,更不用说找到一个用例了。如果您有兴趣,我可以提供有关此行为的更多详细信息。
      猜你喜欢
      • 1970-01-01
      • 2018-05-01
      • 2012-09-09
      • 1970-01-01
      • 2022-01-21
      • 1970-01-01
      • 2011-03-29
      • 1970-01-01
      • 2022-11-02
      相关资源
      最近更新 更多