确定一个点是否在矩形的并集中

时间:2013-11-12 14:50:27

标签: algorithm computational-geometry

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

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

3 个答案:

答案 0 :(得分:4)

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

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的主题,这正是像这样的2D问题所需的数据结构。

稍微低效(但更快)的方法会通过左下角对矩形进行排序(例如) - 然后您只需要搜索矩形的一个子集。

如果坐标是整数类型,则可以创建二进制掩码 - 然后查找是单个操作(在您的情况下,这将需要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.....
................

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

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

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

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

答案 1 :(得分:0)

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

要进行搜索,您必须根据左侧值检查点的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 }
                   };
    }
}

它不漂亮,但应该指向正确的方向。

答案 2 :(得分:0)

我对各种2D几何查询的最爱是Sweep Line Algorithm。它广泛应用于CAD软件中,这是我对程序目的的疯狂猜测。

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

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

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