【问题标题】:Determine if a point is in the union of rectangles确定一个点是否在矩形的联合中
【发布时间】:2013-11-12 14:50:27
【问题描述】:

我有一组轴平行的二维矩形,由它们的左上角和右下角定义(都在整数坐标中)。给定一个点查询,您如何有效地确定它是否在其中一个矩形中?我只需要一个是/否的答案,不需要担心它在哪个矩形中。

我可以通过查看 x 是否在 x1 和 x2 之间以及 y 是否在 y1 和 y2 之间来检查 (x,y) 是否在 ((x1, y1), (x2, y2)) 中。我可以为每个在矩形数量中以线性时间运行的矩形单独执行此操作。但由于我有很多矩形,我会做很多点查询,所以我想要更快的东西。

【问题讨论】:

  • @pablochan 添加到问题中。
  • 如果矩形顶点的x和y坐标不大(即:10^3以内)可以用2D BIT完成
  • @Fallen 他们最多 65535。
  • @marshall,一个简单的 65535 个循环 ifs 大约需要 1-10 毫秒。虽然追求更好的算法是个好主意,但你能先验证一下这个时间对你来说太大了吗?
  • @Shahbaz - 坐标最多为 65k,使得位掩码查找不切实际(如果每个点使用一位,则为 512M)。

标签: algorithm computational-geometry


【解决方案1】:

答案取决于你有多少个矩形。蛮力方法依次检查每个矩形对的坐标:

found = false
for each r in rectangles:
  if point.x > r.x1 && point.x < r.x2:
    if point.y > r.y1 && point.y < r.y2
      found = true
      break

您可以通过将矩形分类为区域并查看“边界矩形”来提高效率。然后,您对不断减小的边界矩形树进行二分搜索。这需要更多的工作,但它使查找 O(ln(n)) 而不是 O(n) - 对于大型矩形集合和许多查找,性能改进将是显着的。您可以在this earlier answer 中看到它的一个版本(它查看一个矩形与一组矩形的交集 - 但您很容易适应“点内”)。更一般地,查看quad trees 的主题,这正是解决此类二维问题所需的数据结构。

一种效率稍低(但速度更快)的方法是按左下角(例如)对矩形进行排序 - 然后您只需搜索矩形的子集。

如果坐标是整数类型,您可以制作一个二进制掩码 - 然后查找是单个操作(在您的情况下,这需要一个 512MB 的查找表)。如果您的空间相对稀疏(即“未命中”的概率非常大),那么您可以考虑使用欠采样位图(例如使用坐标/8) - 然后地图大小降至 8M,如果您有“没有击中”,您可以节省更仔细观察的费用。当然,您必须将左/下四舍五入,并将上/右坐标四舍五入才能使这项工作正常进行。

用一个例子稍微扩展一下:

想象坐标在 x 中可以是 0 - 15,在 y 中可以是 0 - 7。共有三个矩形(全部为[x1 y1 x2 y2][2 3 4 5][3 4 6 7][7 1 10 5]。我们可以在矩阵中绘制它们(我用矩形的编号标记左下角 - 注意 1 和 2 重叠):

...xxxx.........
...xxxx.........
..xxxxx.........
..x2xxxxxxx.....
..1xx..xxxx.....
.......xxxx.....
.......3xxx.....
................

您可以将其转换为一个由 0 和 1 组成的数组 - 这样“此时是否有一个矩形”与“是否设置了此位”相同。一次查找将为您提供答案。为了节省空间,您可以对数组进行下采样 - 如果仍然没有命中,您有答案,但如果有命中,您需要检查“这是真实的” - 这样可以节省更少的时间,并且节省取决于稀疏性你的矩阵(稀疏=更快)。二次采样数组看起来像这样(2x 下采样):

.oxx....
.xxooo..
.oooxo..
...ooo..

我用x 来标记“如果你达到这一点,你肯定会在一个矩形中”,而o 表示“其中一些是一个矩形”。许多点现在都是“可能”,节省的时间更少。如果您进行了更严格的下采样,您可能会考虑使用两位掩码:这将允许您说“整个块都充满了矩形”(即 - 不需要进一步处理:上面的 x)或“需要进一步处理"(如上面的o)。这很快就开始比 Q-tree 方法更复杂...

底线:预先对矩形进行的排序/组织越多,查找的速度就越快。

【讨论】:

  • 如果将时间缩短到 O(log(n)),则基于二进制搜索的解决方案将是完美的。你能解释一下整数掩码的想法吗?
  • @marshall - 更新了二进制算法的链接。
  • 如果每行有 2^16 位并且有 2^16 行,我们最终不是这样使用 2^32 空间吗?
  • @marshall - 是的。每个位置 1 位,即 2^24 字节或 256 MB。很大,但并非不可能。鉴于您设想的矩形和查找的数量,它可能是最有效的(但请注意,事情不会“超级”快,因为您的数组不会在缓存中。)
  • 抱歉有点暗,为什么是2^24而不是2^32?
【解决方案2】:

对于各种 2D 几何查询,我最喜欢的是 Sweep Line Algorithm。它在 CAD 软件中得到了广泛的应用,对于您的程序而言,这将是我的疯狂猜测。

基本上,您将所有点和所有多边形顶点(在您的情况下为所有 4 个矩形角)沿 X 轴排序,然后沿 X 轴从一个点前进到下一个点。如果是非曼哈顿几何图形,您还需要引入中间点,即线段交点。

数据结构是点和多边形(矩形)边与当前X位置的垂直线相交的平衡树,在Y方向上排序。如果结构维护得当,很容易判断当前 X 位置的点是否包含在矩形中:只需检查与点边缘交叉点垂直相邻的 Y 方向。如果允许矩形重叠或有矩形孔,它只是有点复杂,但仍然非常快。

N 个点和 M 个矩形的总体复杂度为 O((N+M)*log(N+M))。人们实际上可以证明这是渐近最优的。

【讨论】:

    【解决方案3】:

    将矩形的坐标部分存储到树结构中。对于任何左值,创建一个指向相应右值的条目,该右值指向相应的顶部值,指向相应的底部值。

    要进行搜索,您必须检查点的 x 值与左侧值。如果所有左值都不匹配,这意味着它们大于您的 x 值,则您知道该点位于任何矩形之外。否则,您检查 x 值与相应左值的右值。同样,如果所有正确的值都不匹配,那么您就在外面。否则与顶部和底部值相同。一旦你找到一个匹配的底部值,你就知道你在任何矩形内并且你已经完成了检查。

    正如我在下面的评论中所说,有很大的优化空间,例如最小左侧和顶部值以及最大右侧和底部值,以快速检查您是否在外面。

    以下方法使用 C#,需要适应您的首选语言:

    public class RectangleUnion
    {
        private readonly Dictionary<int, Dictionary<int, Dictionary<int, HashSet<int>>>> coordinates =
            new Dictionary<int, Dictionary<int, Dictionary<int, HashSet<int>>>>();
    
        public void Add(Rectangle rect)
        {
            Dictionary<int, Dictionary<int, HashSet<int>>> verticalMap;
    
            if (coordinates.TryGetValue(rect.Left, out verticalMap))
                AddVertical(rect, verticalMap);
            else
                coordinates.Add(rect.Left, CreateVerticalMap(rect));
        }
    
        public bool IsInUnion(Point point)
        {
            foreach (var left in coordinates)
            {
                if (point.X < left.Key) continue;
    
                foreach (var right in left.Value)
                {
                    if (right.Key < point.X) continue;
    
                    foreach (var top in right.Value)
                    {
                        if (point.Y < top.Key) continue;
    
                        foreach (var bottom in top.Value)
                        {
                            if (point.Y > bottom) continue;
    
                            return true;
                        }
                    }
                }
            }
    
            return false;
        }
    
        private static void AddVertical(Rectangle rect,
            IDictionary<int, Dictionary<int, HashSet<int>>> verticalMap)
        {
            Dictionary<int, HashSet<int>> bottomMap;
            if (verticalMap.TryGetValue(rect.Right, out bottomMap))
                AddBottom(rect, bottomMap);
            else
                verticalMap.Add(rect.Right, CreateBottomMap(rect));
        }
    
        private static void AddBottom(
            Rectangle rect,
            IDictionary<int, HashSet<int>> bottomMap)
        {
            HashSet<int> bottomList;
            if (bottomMap.TryGetValue(rect.Top, out bottomList))
                bottomList.Add(rect.Bottom);
            else
                bottomMap.Add(rect.Top, new HashSet<int> { rect.Bottom });
        }
    
        private static Dictionary<int, Dictionary<int, HashSet<int>>> CreateVerticalMap(
            Rectangle rect)
        {
            var bottomMap = CreateBottomMap(rect);
            return new Dictionary<int, Dictionary<int, HashSet<int>>>
                       {
                           { rect.Right, bottomMap }
                       };
        }
    
        private static Dictionary<int, HashSet<int>> CreateBottomMap(Rectangle rect)
        {
            var bottomList = new HashSet<int> { rect.Bottom };
            return new Dictionary<int, HashSet<int>>
                       {
                           { rect.Top, bottomList }
                       };
        }
    }
    

    它并不漂亮,但应该为你指明正确的方向。

    【讨论】:

    • 你能用英语解释一下它在做什么吗?我不懂 C#。
    • 你能解释一下这有多“快”吗?
    • 很难用“英语”写它,所以我用 C# 来表达它,但你可以把它想象成一种伪代码。但我会尝试重新编写代码以使其更加自我解释。这个算法的速度在于坐标存储的 tee 结构。我知道,可能存在许多缺点,例如任何矩形都会创建一条路径,或者该点位于最右边的底部等,但更有可能的是该点位于内部某处。有很大的优化空间。此外,这种方法仅在您想根据联合检查许多点时才有用。
    猜你喜欢
    • 1970-01-01
    • 2012-05-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多