给定一组矩形,它们是否有重叠?

20
给定一组矩形,使用元组表示 (xmin, xmax, ymin, ymax),其中 xminxmax 分别代表左右边缘,而 yminymax 分别代表底部和顶部的边缘 - 这个集合中是否存在任何重叠的矩形对?
一种简单的方法是比较每对矩形是否重叠,但这样的时间复杂度是O(n^2) - 可以更好地解决。
更新: xminxmaxyminymax 为整数。因此,矩形1和矩形2重叠的条件是 xmin_2 <= xmax_1 AND xmax_2 >= xmin_1;Y坐标的情况类似。
如果一个矩形包含另一个矩形,则认为这对矩形是重叠的。

4
那么问题是什么? - swingMan
2
问题是,“集合中是否有任何重叠的矩形对”。 - gcbenison
1
列表可以排序吗? - Evorlor
2
@Evorlor 确定;列表没有特定的输入顺序,但算法可以按任何所需的方式对其进行排序。 - gcbenison
7
我不明白为什么你们不理解这个问题。在我看来,这个问题非常直接简单。它是一个算法问题,而不是编码问题。 - Evorlor
显示剩余6条评论
3个回答

13

您可以以O(N log N)的方式完成以下操作。

首先,“挤压”您的y坐标。也就是说,将所有y坐标(顶部和底部)一起排序在一个数组中,然后用排好序的数组中的索引替换矩形描述中的坐标。现在您拥有所有的y都是从0到2n-1的整数,而且问题的答案没有改变(如果您有相等的y,请参见下文)。

现在,您可以将平面分成2n-1个单位高度的条纹,并且每个矩形完全跨越几个条纹。为这些条纹准备一个线段树。(有关线段树概述,请参见this link。)

然后,在同一个数组中按左右边界对所有x坐标进行排序,并保留每个坐标来自哪个矩形以及它是左边界还是右边界的信息。

接着,遍历这个列表,在遍历过程中,维护当前“活动”的所有矩形的列表,也就是说,您已经看到了左边界但是还没有看到右边界的矩形。

更准确地说,在你的线段树中,你需要为每个条纹保留多少个覆盖它的活动矩形。当你遇到左边界时,你需要为相应矩形底部和顶部之间的所有条纹添加1。当你遇到右边界时,你需要减去1。使用线段树的质量更新(延迟传播),可以在O(log N)内完成加法和减法。
实际上检查你所需的内容,当你遇到左边界时,在添加1之前,请检查底部和顶部之间是否至少有一个具有非零覆盖范围的条纹。这可以通过在线段树中执行间隔查询的总和来在O(log N)内完成。如果此间隔上的总和大于0,则存在交叉部分。
 squeeze y's
 sort all x's
 t = segment tree on 2n-1 cells
 for all x's
     r = rectangle for which this x is
     if this is left boundary
         if t.sum(r.bottom, r.top-1)>0   // O(log N) request
              you have occurence
         t.add(r.bottom, r.top-1, 1)  // O(log N) request
     else
         t.subtract(r.bottom, r.top-1)  // O(log N) request

你应该仔细实现它,考虑是否认为触摸是相交的,这将影响你对相等数字的处理。如果你认为触摸是相交的,那么你所需要做的就是,在排序y时,确保所有具有相同坐标的点中,所有顶部都在底部之后,同样地,在排序x时,确保所有相等的x中,所有左边的都在右边之前。

1
你需要检查的不仅仅是集合中是否已经包含了顶部和底部之间的值:可能当前正在考虑左边缘的矩形与一个“活动”矩形相交,该矩形从上方开始并在下方结束。 - j_random_hacker
1
如果你只从左到右遍历,那么时间复杂度是O(n^2)。考虑所有矩形具有相同的左边缘但高度不同的简单情况。 - Tibos
@Petr关于“touches”的观点很好。我编辑了问题,以指定输入为整数,并澄清了什么被认为是重叠的。 - gcbenison
1
@BrentWashburne,是的,但它可以很容易地在O(N log N)内完成。 - Petr
1
@j_random_hacker,看来我有些错误,线段树是这种数据结构的更正确的名称。维基百科对于线段树和区间树都有相当奇怪的页面,描述的并不是我想要的,但在谷歌上搜索线段树会出现许多适当的链接。其中http://se7so.blogspot.ru/2012/12/segment-trees-and-lazy-propagation.html也提到了懒惰传播。是的,Fenwick树在这里也可以使用。 - Petr
显示剩余6条评论

6
为什么不尝试平面扫描算法?平面扫描是计算几何中广泛使用的设计范例,因此它具有良好的研究基础和大量在线文档可用。请看this。线段相交问题应该能给你一些想法,还有矩形并集的面积。

阅读关于Bentley-Ottman算法的线段交集问题,这个问题与您的问题非常相似,时间复杂度为O((n+k)logn),其中k是交点数。然而,由于您的矩形边平行于x和y轴,所以它更加简单,因此您可以修改Bentley-Ottman算法使其在O(nlogn +k)内运行,因为您不需要更新事件堆,一旦访问矩形,所有交点都可以被检测出来,且不会修改扫描线的顺序,因此不需要维护事件。为了检索与新矩形相交的所有矩形,建议对每个矩形的ymin和ymax使用range tree,这将给出所有位于由新矩形的ymin和ymax定义的区间内的点,从而得到与之相交的矩形。

如果您需要更多细节,您应该查看M. de Berg等人的第二章计算几何书。同时,请查看这篇论文,它展示了如何在O(nlogn + k)中找到凸多边形之间的所有交点,这可能比我上面的建议更简单,因为所有数据结构都在那里解释了,而且您的矩形是凸的,在这种情况下非常好。

2
我曾考虑返回所有交点,但后来意识到只需确定是否至少存在一个交点。在这种情况下,找到第一个交点就足够了,所以忽略我的答案中的k。此外,在2d范围树中进行n个查询可能会更快,并且一旦找到一个交点就可以停止。复杂度将为O(nlog^2n),但使用分数级联可将其降至O(nlogn)。R树也可能证明有用。 - Javier Cano
如果您知道那个O(nlog n + k)算法用于查找凸多边形之间所有交点是否可以“限制”为一个O(nlog n)算法用于查找单个交点,那就太好了。它可能可以,但这并不是自动的...而且我懒得自己读论文 :-P - j_random_hacker
这个过程与查找所有的过程相同,但是当你找到第一个时,就停止算法并返回true。如果在没有找到任何一个的情况下结束了该过程,则返回false。在这两种情况下,您都需要对x坐标进行排序,并且最多需要对区间树进行n次查询,因此无法避免O(nlogn)的复杂度,而k来自于找到的交点数量,因此k最多为1。 - Javier Cano
从您上一条评论中的内容来看,这个算法确实可以在O(nlog n)的时间内找到1个交点--我的观点是,在没有关于特定算法的信息的情况下,它是O(nlog n + k)来找到所有k个交点,并不意味着可以在O(nlog n)时间内找到1个交点。它确实意味着可以在O(nlog n)时间内返回“否”答案,但如果k>0,则我认为仍然有可能存在一个O(nlog n + k)算法,以某种方式支付ω(1)成本只是为了找到其中任何一个:这个成本可以分摊到后面的k-1个交点上。 - j_random_hacker
1
该算法由两个主要步骤组成,第一步是对矩形的x坐标进行排序,然后按照这个顺序处理每个矩形。当您首次看到一个矩形时,您可以在O(logn)时间内将其添加到二叉树中,并通过添加它来检测是否与先前看到的任何其他矩形相交。您可以在O(k')中报告每个相交的矩形,或者只在O(1)中计算数量。当您看到矩形的结尾时,您可以在O(logn)中从树中删除它。因此,如果不报告相交,则第二个主要步骤的总成本为O(nlogn),加上排序的O(nlogn)。 - Javier Cano

-1

通过构建一个不重叠的矩形新列表,您可以做得更好。从矩形集合中取出第一个矩形并将其添加到列表中。显然它不会与任何其他矩形重叠,因为它是列表中唯一的一个。从集合中取出下一个矩形,并查看它是否与列表中的第一个矩形重叠。如果是,则返回true;否则,将其添加到列表中。对集合中的所有矩形重复此操作。

每次,您都在将矩形r与列表中的r-1个矩形进行比较。这可以在O(n*(n-1)/2)O((n^2-n)/2)中完成。您甚至可以将此算法应用于原始集合,而无需创建新列表。


4
这仍然是O(N^2),实际上比直接检查所有n*(n-1)/2对(如果找到交集则立即停止)要慢。 - Petr

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接