我有 N 矩形,其边与x轴和y轴平行。还有另一个矩形,模型。我需要创建一个算法来判断模型是否被 N 矩形完全覆盖。
我有一些想法。我认为首先,我需要按左侧对矩形进行排序(可以在 O(n log n)时间内完成),然后使用垂直扫描线。
+------------------------------------------------------------> x
|O
| +----+
| +---------+ | |
| | ++----+--+ |
| | +-++----+-+| |
| | | | +-++-+
| +------+ +-------++
| +---------+
|
|
|
|y
蓝色矩形是模型。
首先,我需要抽象算法。对于实现没有特殊要求。矩形可以表示为一对点(左上角和右下角)。
这是准备考试的任务之一。我知道最好的算法可以在 O(n log n)时间内完成。
答案 0 :(得分:6)
这是一种分而治之的算法。 AVERAGE案例的复杂性非常好,我会说它类似于O(n log MaxCoords)
。虽然最坏的情况可能是二次的,但我认为这样的测试很难创建。为了使它更难,使递归函数的顺序调用随机。也许@Larry提出的建议平均可以得到O(n log n)
。
我无法弄清楚扫描线解决方案,但对于我尝试过的测试,这非常快。
基本上,使用递归函数可以在蓝色矩形上工作。首先检查蓝色矩形是否被其他矩形完全覆盖。如果是的话,我们已经完成了。如果没有,将其拆分为4个象限,并递归调用这些象限上的函数。所有4个递归调用必须返回true。我包含了一些绘制矩形的C#代码。您可以将其更改为使用更大的值,但在这种情况下请删除绘图过程,因为这将永远需要。我用一百万个矩形测试了它,并且生成了一个十亿边的正方形,使得它没有被覆盖,并且提供的答案(false
)在四核上花了大约一秒钟。
我主要在随机数据上对此进行了测试,但似乎是正确的。如果事实证明它不是我会删除它,但也许它会让你走上正确的道路。
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
List<Rectangle> Rects = new List<Rectangle>();
private const int maxRects = 20;
private void InitRects()
{
Random rand = new Random();
for (int i = 0; i < maxRects; ++i) // Rects[0] is the model
{
int x = rand.Next(panel1.Width);
int y = rand.Next(panel1.Height);
Rects.Add(new Rectangle(new Point(x, y), new Size(rand.Next(panel1.Width - x), rand.Next(panel1.Height - y))));
}
}
private void DrawRects(Graphics g)
{
g.DrawRectangle(Pens.Blue, Rects[0]);
for (int i = 1; i < Rects.Count; ++i)
{
g.DrawRectangle(Pens.Red, Rects[i]);
}
}
private bool Solve(Rectangle R)
{
// if there is a rectangle containing R
for (int i = 1; i < Rects.Count; ++i)
{
if (Rects[i].Contains(R))
{
return true;
}
}
if (R.Width <= 3 && R.Height <= 3)
{
return false;
}
Rectangle UpperLeft = new Rectangle(new Point(R.X, R.Y), new Size(R.Width / 2, R.Height / 2));
Rectangle UpperRight = new Rectangle(new Point(R.X + R.Width / 2 + 1, R.Y), new Size(R.Width / 2, R.Height / 2));
Rectangle LowerLeft = new Rectangle(new Point(R.X, R.Y + R.Height / 2 + 1), new Size(R.Width / 2, R.Height / 2));
Rectangle LowerRight = new Rectangle(new Point(R.X + R.Width / 2 + 1, R.Y + + R.Height / 2 + 1), new Size(R.Width / 2, R.Height / 2));
return Solve(UpperLeft) && Solve(UpperRight) && Solve(LowerLeft) && Solve(LowerRight);
}
private void Go_Click(object sender, EventArgs e)
{
Graphics g = panel1.CreateGraphics();
panel1.Hide();
panel1.Show();
Rects.Clear();
InitRects();
DrawRects(g);
textBox1.Text = Solve(Rects[0]).ToString();
}
答案 1 :(得分:1)
这是一个通用算法
现在的问题是如何有效地完成上述工作。以上可以在所有多边形的单个循环中完成,所以我认为你正在看O(n)时间。
如果您需要创建能够测试多个模型的高效算法,或者您必须优化以获得最快的答案(以牺牲准备数据为代价),那么您正在寻找一种结构,以便快速回答问题矩形与矩形相交(或包含)。
如果您使用,例如kd-trees,我相信这可以在O(log n)时间内得到解答 - 但此算法中的重要变量也是找到的矩形k的数量。你最终会得到像O(k + log n)这样的东西,你还需要做一个联合部分来测试模型是否被覆盖。
我的猜测是联合可以用O(k log k)计算,所以如果k很小,那么你看O(log n),如果k与n相当则那么它应该是O(k log) K)。
另见this问题。
编辑: 为了应对交叉点和工会的复杂性。
更详细地说,我们有两种情况,具体取决于k&lt;&lt; n或k与n相当
a)在k <&lt;&lt; n并假设交集/并集的多项式复杂度(所以这里我放弃猜测O(k log k))我们有:
总数为O(k + log n + f(k)),直接等于O(log n),因为大O仅取决于增长最快的项。
在这种情况下,算法中更重要的部分是找到多边形。
b)在k与n相当的情况下,我们必须看一下交集和并集算法的复杂性
(注意这里的并行RDBM如何根据选择性使用索引或进行表扫描;它与我们这里的相似选择。)
如果k足够大,则算法变得不再是用于找到与矩形相交的矩形的算法,并且变得更像是用于计算多边形的并集的算法。
但是,我相信kd树也可以帮助找到段的交集(这是构建联合所必需的),所以即使这部分算法也可能不需要k ^ 2时间。 在这一点上,我将研究用于计算联合区域的有效算法。
答案 2 :(得分:1)
你在扫描线的正确轨道上。从概念上讲,我们希望检测模型与扫描线的交叉点何时未被其他矩形覆盖。高级模板是将每个矩形分成“左边缘”和“右边缘”事件,按x坐标对事件进行排序(如果矩形关闭则将左侧放置在权限之前,如果打开则放置左侧之前的权限),然后在O(log n)时间内处理每个事件。这基本上是功课,所以我不再说了。
答案 3 :(得分:1)
有一种简单的O(N^2)
方法类似于提出的“光栅”方法。由于所有矩形都是轴平行的,因此最多只能有2N
个不同的x和y维度。对所有x和y进行排序并重新映射:x_i -> i
。现在你有一个2N x 2N
矩阵,你可以轻松地使用天真的O(N^2)
算法。
答案 4 :(得分:1)
我一直在思考它,我想我终于理解了sweep line @algorithmist
的含义。但是sorting
操作的存在意味着我有:
O(n log n)
平均值 O(n**2)
扫描线
首先,我们需要一些排序,因为我们的sweep line
将包含一个有序集合。
此有序集将包含每个top
的{{1}}和bottom
行,只要它们位于red
和top
之间bottom
。这会将我们的空间划分为(最多)blue
个水平条。
现在,我们需要确保在每个n*2
中覆盖整个strip
,并且此操作的复杂度不能超过blue
,如果我们可以这样做(对于每个条带)有一个覆盖间隔的列表。 这是“事件”O(log n)
所说的
为了处理这个事件,我们将使用下面描述的二叉树来处理在完全@algorithmist
操作中添加或删除矩形并产生树所覆盖的最右边的间隔,我们用它来判断条带是否正确是否涵盖了O(log n)
。
二叉树
首先,我将索引blue
矩形。我们使用这个函数对它们进行排序:
red
然后我要创建一个专用的二叉树。
def __lt__(lhs, rhs):
return (lhs.left < rhs.left)
or (lhs.left == rhs.left and lhs.right < rhs.right)
个叶子,每个叶子代表一个N
矩形并指示其存在与否。它们是根据索引订购的。处理错误“代码块不能跟随列表”:
red
现在我们有两种可能性,让孩子们说class Node:
def __init__(self):
self.interval = []
self.left = None
self.right = None
和[a,b]
:
[c,d]
,则节点会保留c <= b
[a,d]
为什么会这样?让我们以4片叶子为例:
[c,d]
特殊节点忽略 _ [1,9] _
/ \
[1,7] [6,9] <-- Special node merge
/ \ / \
/ \ / \
[1,3] [2,7] [3,5] [6,9]
,因为它不是最右边的间隔。原因是矩形是有序的:
[3,5]
右侧的任何矩形都不会涵盖丢失的[6,9]
区间,因为它们是在[5,6]
之后开始的6
左侧的矩形在[3,5]
之前开始,因此,如果它们覆盖了缺失的3
,则无论如何都会覆盖[5,6]
根可能不会指示所涵盖的确切间隔集:仅覆盖最右边的间隔。但是,我们完全可以判断[3,5]
是否完全被覆盖了!
此树上有2个操作:
每个都相似:
递归位取blue
。这是平衡二叉树的经典属性。一旦完成,我们就会有一个由根覆盖的最右边的间隔,足以判断O(log n)
段是否完全被覆盖。
<强>复杂性强>
算法的复杂性很简单:
blue
次活动O(n)
核心部分产生O(log n)
。
但是,我们不应忘记我们还有2个O(n log n)
操作:
每个都应在平均值中取sort
,但在最坏的情况下可能会退化为O(n log n)
,具体取决于所使用的排序算法。
答案 5 :(得分:1)
好吧,现在看来我甚至不能在晚上睡觉,因为我想到了这个问题...但似乎我终于得到了一个 O(n log n)解决方案,在<强>平均的情况(但与@lVlad
相比,病理输入的几率降低。)
我们都知道 Divide and Conquer 技术。为确保O(n log n)
使用它,我们通常关注2点:
O(n)
考虑到这些限制因素,我已经详细阐述了以下算法,它让人想起qsort
,因此遭遇同样的陷阱(即分形输入)。
<强>算法强>
red
相交的blue
部分,将它们插入HashSet中以删除重复项 - &gt; O(n)
O(n)
red
放在分区中,应用Clipping技术,请注意给定的red
最终可能会在不同的分区中提供多个块 Pivot Choice 是算法的基石,如果分区不合适,我们无法达到所需的复杂性。此外,它必须在O(n)
中完成。到目前为止我有2个提案:
Maximum Area
:使用面积最大的矩形,因为这意味着之后分区的面积最小,但是它很容易胜过Median of 3
:基于qsort,选取3个元素,选择中位数(中心靠近3个中心的重心)我建议将它们混合起来:
实现的另一个方面是递归的 Tail 。与qsort
一样,对于小n
,该算法很可能效率低下。我建议使用introsort
技巧,而不是一直到1,如果n
小于12,则使用以下算法:
red
(仅边缘)并对它们进行排序(ala introsort)维度不可知
该算法被正式定义为适用于具有相同渐近复杂度的任何给定维度,平均O(n log n)。这使我们有机会在维度1中进行测试以识别病理输入。
病理输入
与模拟它的qsort
一样,对于阶乘输入是明智的。通过阶乘输入我的意思是:
1.......6...9.11.13
每当你选择你的间隔的平均值时,你就会在它的一边有所有元素。
有了这样的输入,即使选择中位数3也不太可能产生非常好的效果。
修改强>
我将用一个小方案来展示分区的想法,因为@lVlad
注意到它有点模糊。
+----------------+----+---------+
| 1 | 2 | 3 |
+----------------+----+---------+
| 8 | P | 4 |
+----------------+----+---------+
| 7 | 6 | 5 |
+----------------+----+---------+
好的,我们检查覆盖范围的矩形现在被分成9个子矩形。我们忽略P,它是我们的支点。
现在,我们真的希望每个子矩形都比red
少N
。选择枢轴作为中心的重心,这意味着如果我们的“随机”选择确实在枢轴的每个方向上有大约red
个中心。
它有点模糊,因为一些特殊的配置可能会使它在一个步骤中获得很少的增益(所有矩形具有相同的中心,我们只选择较小的一个),但它会产生混乱,因此以下步骤将有所不同。
如果有人能够正式确定,我很高兴,我是工程师,而不是计算机科学家,我的数学落后......
答案 6 :(得分:0)
很难知道你在寻找什么,但听起来像R-tree可能有效吗?
答案 7 :(得分:0)
好的,我已经问了足够的问题,这里有一些答案......
如果数据表示为栅格,则算法很简单:
如果数据是矢量,那就有点复杂了。首先定义一个函数,该函数返回表示两个矩形的交集(如果有)的矩形。这很简单。然后继续:
再次,只打扰与蓝色矩形相交的红色矩形。对于每个红色矩形,计算矩形与UnCoveredRectangle的交集。交叉点将导致以下情况之一:
2.1交点等于UnCoveredRectangle。工作结束了。
2.2交叉点'咬住'CoveredRectangle中的矩形块。剩余的UnCoveredRectangle将是矩形,L形片或U形片。如果它本身是一个矩形,则将UnCoveredRectangle设置为剩余的矩形,然后转到步骤2.如果UnCoveredRectangle是L形或U形,则将其拆分为2或3个矩形并从步骤2递归。
如果在UnCoveredRectangle的(部分)区域被发送到0之前用完红色矩形,则表示您已完成。
好吧我对这个算法的复杂性一无所知,但除非矩形的数量很大,我不太关心,尽管也许@den是。而且我省略了很多细节。我不能像@den那样绘制漂亮的图表,所以你必须为自己画出来。
答案 8 :(得分:0)
答案 9 :(得分:0)
以下是如何使扫描线在O(n lg n)中工作。我将集中讨论BST如何运作的棘手部分。
保持平衡的BST,其中包含与当前扫描线相交的每个矩形的起点和终点。 BST的每个节点包含两个辅助字段:minOverlap和deltaOverlap。字段minOverlap通常存储与该节点的子树所覆盖的间隔中的任何点重叠的最小矩形数。但是,对于某些节点,该值略有偏差。我们保持一个不变量,即minOverlap加上delta到根的每个节点的deltaOverlap总和都有与节点子树中某个区域重叠的真实最小矩形数。
当我们插入一个矩形起始节点时,我们总是插入一个叶子(并可能重新平衡)。当我们遍历插入路径时,我们将任何非零deltaOverlap值“下推”到插入节点的访问路径的子节点,更新访问路径上节点的minOverlap。然后,我们需要在O(lg n)时间内将每个节点递增到树中插入节点的“右”。这是通过递增插入节点的所有右祖先的minOverlap字段并递增插入节点的右祖先的所有右子节点的deltaOverlap来实现的。执行类似的过程以插入结束矩形的节点,以及删除点。可以通过仅修改旋转中涉及的节点的字段来执行重新平衡操作。您所要做的就是检查扫描中每个点的根,看看minOverlap是否为0。
我遗漏了处理复制坐标之类的细节(一个简单的解决方案只是在相同坐标的任何关闭矩形节点之前对开放矩形节点进行排序),但希望它能给你提供想法,并且相当有说服力。
答案 10 :(得分:0)
您是否知道the area of the union of rectangles通常的最坏情况O(nlogn)
算法?
你需要做的就是计算两个方面:
如果这些区域相同,则完全覆盖模型,否则不是。
答案 11 :(得分:-1)
这是使用一些内存的O(n lg n)运行时方法。
使用示例:
我们只对包含'model'矩形的场景的子部分感兴趣;在此示例中,“模型”矩形为1,1 -> 6,6
1 2 3 4 5 6 1 +---+---+ | | 2 + A +---+---+ | | B | 3 + + +---+---+ | | | | | 4 +---+---+---+---+ + | | 5 + C + | | 6 +---+---+
1)收集模型矩形(左右)范围内的所有x坐标到列表中,然后对其进行排序并删除重复项
1 3 4 5 6
2)收集模型矩形(顶部和底部)范围内的所有y坐标到列表中,然后对其进行排序并删除重复项
1 2 3 4 6
3)通过唯一的y坐标之间的间隙数*创建一个二维数组*唯一的y坐标之间的间隙数。这可以在每个单元格中使用一位,您可以考虑使用C ++ STL的bit_vector()来实现高效的表示。
4 * 4
4)将所有矩形绘制到此网格中,将其绘制在单元格上:
1 3 4 5 6 1 +---+ | 1 | 0 0 0 2 +---+---+---+ | 1 | 1 | 1 | 0 3 +---+---+---+---+ | 1 | 1 | 2 | 1 | 4 +---+---+---+---+ 0 0 | 1 | 1 | 6 +---+---+
5)如果任何细胞仍未上漆,您知道您的模型并未完全遮挡(或者您正在测试的任何细胞)。
采集坐标和绘画步骤为O(n),坐标的排序为O(n lg n)。
这是根据我的一个答案改编的:What is an Efficient algorithm to find Area of Overlapping Rectangles