【问题标题】:Rectangles Covering矩形覆盖
【发布时间】:2010-04-13 08:41:33
【问题描述】:

我有 N 个边平行于 x 轴和 y 轴的矩形。还有另一个矩形,model。我需要创建一个算法来判断 model 是否完全被 N 个矩形覆盖。

我有一些想法。我认为首先,我需要将矩形按左侧排序(可以在 O(n log n) 时间内完成),然后使用垂直扫描线。

+------------------------------------------------------------> x
|O
|                  +----+
|   +---------+    |    |
|   |        ++----+--+ |
|   |      +-++----+-+| |
|   |      | |     +-++-+
|   +------+ +-------++
|          +---------+
|
|
|
|y

蓝色矩形是模型

首先,我需要抽象算法。对于实现没有特殊要求。一个矩形可以表示为一对点(左上和右下)。

这是准备考试的任务之一。我知道最好的算法可以在 O(n log n) 时间内做到这一点。

【问题讨论】:

  • 也许一张小图会有助于理解你的 Ox 和 Oy 是什么意思
  • 你的矩形是如何表示的?您是否将矩形的边界视为矩形内部,还是仅视为矩形的内部? N 有多大(大约)? N个矩形中的任何一个是否相互交叉?您告诉我们的越多,您获得的帮助就越有用。
  • 这是一次性计算,还是您会针对许多“模型”检查相同的 N 个矩形?
  • 我只有一个模型-矩形。
  • 矩形的位置是完全随机的

标签: algorithm geometry rectangles


【解决方案1】:

这是一个分而治之的算法。平均案例复杂度非常好,我会说它类似于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 坐标”矩形覆盖模型矩形上的每个点,或者它们的某种组合。)在最坏的情况下,它将是 O(r * k log k),其中 r = 矩形的数量, k 是模型矩形上的坐标数。不适用于浮点坐标,因为您必须声明任意精度(“坐标”的大小,在您的情况下为 3x3 块),但仍然非常好。 :)
  • 我对区间树、Fenwick 树和comp geo 中的其他一些变体都了如指掌,但也许我太傻了,无法匹配。我也有一些预期的O( N log N ) 算法,它们是最坏的情况O(N^2)。我真的很好奇答案!
【解决方案2】:

您在扫描线的正确轨道上。从概念上讲,我们想要检测模型与扫描线的交点何时未被其他矩形覆盖。高级模板是将每个矩形分成“左边缘”和“右边缘”事件,按 x 坐标对事件进行排序(如果矩形关闭则将左放在右之前,如果矩形打开则将右放在左之前),然后在 O(log n) 时间内处理每个事件。这基本上是功课,我就不多说了。

【讨论】:

  • 这是我在我的问题上看到的最好的补充,谢谢。我和你也是这样想的。
  • 您能描述一下如何处理这个事件吗?你如何在 O(log n) 中处理它?这是最重要的事情,很明显你必须排序...... @den:我建议你在接受任何答案之前再等一会儿,不管有多大帮助。让其他人也有机会发布内容。
  • @IVlad:与大多数扫描线算法一样,它涉及平衡二叉树。
  • @algorithmist:这仍然不是很有帮助。您只是在复制理论文本,问题是如何在这个特定问题上使用它。
  • 这就是扫线的意思
【解决方案3】:

这是一个通用算法

  1. 是否有任何矩形覆盖整个模型?
    如果是,你就完成了
  2. 如果没有,是否有任何矩形部分覆盖了模型?
    如果是的话
  3. 是矩形与模型的所有交点的并集等于模型
    如果 2. 或 3. 为否,则答案为否,否则为是

现在的问题是如何有效地完成上述工作。以上可以在所有多边形的单个循环中完成,所以我认为您正在查看 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

a) 在 k

  • 在 kd 索引树(矩形)中将 n 记录到 find a range
  • k 步检索该“分支”中的所有矩形,
  • f(k) 一些多项式时间来计算交集和并集

总数为O(k + log n + f(k)),直接等于O(log n),因为大O只取决于增长最快的项。

在这种情况下,算法更重要的部分是寻找多边形。

b) 在 k 与 n 可比的情况下,我们必须看看交集和并集算法的复杂度 (请注意这里关于 RDBM 如何根据选择性使用索引或进行表扫描的平行;这与我们在这里的选择类似)。
如果 k 足够大,该算法就不再是一种用于寻找与矩形相交的矩形的算法,而更像是一种用于计算多边形并集的算法。

但是,我相信 kd 树也可以帮助找到段的交集(这是建立联合所必需的),所以即使这部分算法也可能不需要 k^2 次。 在这一点上,我将研究计算联合面积的有效算法。

【讨论】:

  • 您确定可以在 O(N) 时间内合并 N 个交叉点吗?第三点中的问题与原始问题基本相同,1 和 2 只是您可以在 O(N) 中解决的微不足道的情况。随着合并更多矩形,多边形的复杂性会增加,从而导致 O(N²) 操作。不过,我应该说,我的经验有限,并且可能正在考虑一种幼稚的方法。
  • 不,我说高效算法可以通过 O(n log n) 做到这一点。
  • @Kobi:交点的联合将在 O(f(k)) 时间内发生,对于 k
  • 我很抱歉,但我认为在这里添加 k 根本没有帮助。您试图将问题从 n 减少到 k,但最终还是遇到了同样的问题!实际上,您不妨假设所有矩形都与模型相交;过滤其他矩形是微不足道的,可以在“免费”的第一步完成(即 O(N) 在 O(NlogN) 算法上)。当然,如果相关输入要小得多,则解决方案很简单,但复杂性大约是最坏的情况......
  • @Kobi,经过一番思考,我认为您是对的,看来我在那里切线了;但我仍然没有说任何错误,只是没用:)(实际上我只分析了一个特殊情况,在这种情况下,如果你不计算 O(n log n) 的复杂度,你会得到 O(log n)建立一个kd树)。为您的评论 +1
【解决方案4】:

有一种简单的O(N^2) 方法,类似于提出的“光栅”方法。由于所有矩形都是轴平行的,因此最多只能有 2N 不同的 x 和 y 维度。对所有 x 和 y 进行排序并重新映射:x_i -&gt; i。所以现在你有了一个2N x 2N 矩阵,你可以在其中轻松使用简单的O(N^2) 算法。

【讨论】:

  • +1 表示重新映射坐标,-1 因为这仍然是二次的。
  • 没错,我认为这会有所帮助。没有人发布“真正的”O(N log N) 解决方案,并且操作似乎不需要该解决方案。我还写了一些关于明显事件及其处理的内容,但无法深入到 O(log N) 检查 - 充其量是 O(k) 输出敏感。无论哪种情况,这都是一个完全可行的低常数解决方案,实用且易于实施。我认为我们没有必要只发布“最佳”解决方案,否则 Chazelle 的 O(N) 简单多边形三角剖分不会为 O(N log N) 算法留出空间,尽管这是不可行的。
  • 此外,我并不隐瞒这个事实。 =) 我相信这是最简单的O(N^2) 解决方案 - 否则请告诉我。
  • 我也同意这是最简单的。但是,我仍然希望有人最终能找到O(n log n) 的解决方案。我对此很感兴趣,因为我尝试过的任何事情似乎都无法让我到达任何地方。
  • 我也是,但在那之前! =) 我已经使用明显的扫描线算法玩了一些数据结构,但没有任何结果。
【解决方案5】:

好吧,现在看来我晚上都睡不着了,因为我想到了这个问题……但似乎我终于得到了一个 O(n log n) 的解决方案,在 平均案例(但与@lVlad相比,病理输入的机会减少了)。

我们都知道分而治之技术。为了保证O(n log n)使用它,我们通常关注2点:

  • dividemerge 应该是O(n)
  • 存在 C > 1,因此在每一步中,子问题的大小都会减少一个因子 C(在整个问题中保持不变)

考虑到这些限制,我详细阐述了以下算法,它让人想起 qsort,因此遭受相同的陷阱(即分形输入)。

算法

  1. 剪辑:我们只考虑red 中与blue 相交的部分,它们被插入到HashSet 中以删除重复项--> O(n)
  2. 枢轴选择:稍后会详细介绍 --> O(n)
  3. 分区:一旦有了轴,我们将空间细分为 3d 个区域,其中一个是轴,d 是维度(d = 1 表示分段, 2 用于矩形,3 用于立方体等...)
  4. 重新分区:我们将red 放在分区中,应用剪辑技术,注意给定的red 最终可能会在不同的分区中提供多个块
  5. 递归:我们在每个分区上递归应用(从第 2 步开始),从人口最少的分区开始,一旦没有覆盖就停止

枢轴选择是算法的基石,如果分区不合适,我们将无法达到所需的复杂度。此外,它必须在O(n) 中完成。到目前为止,我有 2 个提案:

  • Maximum Area: 使用面积最大的矩形,因为后面的分区面积最小,但是容易被王牌打倒
  • Median of 3:基于qsort,取3个元素,选择中位数(3个中心中离重心较近的那个)

我建议将它们混合起来:

  • 选取面积最大的3个元素,选择中位数,在pivot处使用
  • 如果重新分区后发现其中一个分区填充了超过 N/C(待定制)元素,则随机选取 3 个元素,选择中间值,然后进行重新分区(此处不检查)

实现的另一个方面是递归的Tail。就像qsort 一样,该算法可能对小的n 效率低下。我建议不要一直到 1,而是使用 introsort 技巧:如果 n 小于 12,则使用以下算法:

  • 对于每个轴,将每个 red 投影到轴上(仅边缘)并对它们进行排序(ala introsort)
  • 这定义了 nd 个区域的栅格,检查每个区域是否都被覆盖

维度无关

该算法被正式定义为适用于具有相同渐近复杂度的任何给定维度,平均 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。枢轴被选为中心的重心,因此这意味着如果我们的“随机”选择成立,那么在枢轴的每个方向上大约有同样多的reds 中心。

那里有点模糊,因为一些特殊的配置可能会导致一步的增益很小(所有矩形都有相同的中心,我们只选择了较小的一个),但它会造成混乱,因此接下来的步骤会有所不同。

如果有人能将其正式化,我很高兴,我是一名工程师,而不是计算机科学家,而且我的数学落后...

【讨论】:

  • 我不太明白。您如何确保与每个递归调用对应的子矩形不生成(或很少生成)O(n) 检查?你能发布一些伪代码吗?
  • 好的,我编辑了。当我们使用第一个分区方案(最大面积)时很明显,因为我们检查没有子问题的输入超过 n/c(例如c == 2)。但是我必须承认,尽管由于选择了P 是因为它的居中位置,所以应该可以证明这一点,但很难证明重新分区会起作用。可能是因为在某些情况下(例如分形)它不会。
  • 也许是我,但我根本不明白你的方法:)。我知道您尝试将蓝色矩形拆分为子矩形,但是您如何处理这些?您检查它们是否完全包含在一个蓝色矩形内,不是吗?您是否有办法在每个递归级别的O(N) 中执行此操作,而不是在每个递归调用的O(N) 中执行此操作?您谈论通过预测将问题减少到 1d,但我不确定这些将如何工作......
【解决方案6】:

我一直在想它,我想我终于明白了@algorithmist 的意思是sweep line。然而,sorting 操作的存在意味着我有:

  • O(n log n) 平均
  • O(n**2)最坏的情况

扫描线

首先,我们需要一些排序,因为我们的sweep line 将包含遍历有序集。

只要它们位于bluetopbottom 之间,这个有序集合就会包含reds 的topbottom 行。这将我们的空间划分为(最多)n*2 水平条。

现在,我们需要确保在每个 strip 中,覆盖整个 blue,并且此操作的复杂性不能超过 O(log n),如果我们有(对于每个条带),则可以这样做覆盖区间的列表。 这是@algorithmist 所说的“事件”

为了处理这个事件,我们将使用下面描述的二叉树,它在 O(log n) 操作中处理添加或删除一个矩形,并产生树覆盖的最右边的间隔,我们用它来判断 @ 的条带是否987654339@是否被覆盖。

二叉树

首先,我将索引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 &lt;= b,则节点持有[a,d]
  • 否则它持有[c,d]

为什么有效?我们以 4 片叶子为例:

        _ [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] 反正

根可能不表示所涵盖的确切区间集:仅涵盖最右边的区间。但是,我们完全可以判断blue 是否被完全覆盖!

这棵树上有 2 个可用的操作:

  • 将矩形标记为存在
  • 将矩形标记为不存在

每个都相似:

  • 如果矩形已经处于这种状态,什么也不做
  • 否则,切换其状态,然后更新其父区间(递归,直到根)

递归位采用O(log n)。这是平衡二叉树的经典属性。一旦完成,我们就有了被根覆盖的最右边的区间,这足以判断 blue 段是否被完全覆盖。

复杂性

算法的复杂性很简单:

  • 我们有O(n) 活动
  • 每个事件都在O(log n) 中处理

核心部分产生O(n log n)

但是,我们不应该忘记我们还有 2 个sort 操作:

  • 对事件进行分类(用于扫描线)
  • 另一个对矩形进行分类(用于二叉树)

每个都应采用平均中的O(n log n),但在最坏的情况下可能会退化为O(n**2),具体取决于所使用的排序算法。

【讨论】:

  • 我认为问题不在于插入/删除,而是检查整个蓝色间隔被覆盖并在O(log N)时间仍然覆盖的每一步。至少,我在做这件事时遇到了麻烦,除非我遗漏了一些明显的东西。
  • 我明白了,文章说“查询时间是 O(log n)”所以我认为这将是简单的部分,但我认为它只是指查询一个点(而不是一个间隔)被覆盖。
  • 好的,我为这个问题创建了一个新结构,它保证O(log n) 操作可以添加或删除一个间隔。这是基于我们事先知道手头的间隔这一事实。
  • 关于排序:如果我们使用堆排序,我们可以通过 O(n log n) 做到这一点。存在的 teorema 最好是 O(n log n)。
【解决方案7】:

很难知道您在寻找什么,但在我看来 R-tree 可能有用吗?

【讨论】:

  • 这似乎很有用,但我不知道如何做到这一点。
【解决方案8】:

好的,我已经问了足够多的问题了,这里有一个答案......

如果将数据表示为栅格,则一种算法很简单:

  1. 创建一个表示模型(即蓝色)矩形的布尔数组。将所有元素设置为 FALSE(表示“未覆盖”)
  2. 对于每个红色矩形(忽略不能与蓝色重叠的那些),如果数组的所有元素都在红色矩形内,则将它们设置为 TRUE。
  3. 最后,检查数组的每个元素是否都设置为 TRUE。

如果数据是矢量,则稍微复杂一些。首先定义一个函数,该函数返回表示两个矩形的交集(如果有)的矩形。这很简单。然后继续:

  1. 创建一个名为“UnCoveredRectangle”的变量,该变量被初始化为与蓝色矩形相同。
  2. 再一次,只关心与蓝色矩形相交的红色矩形。对于每个红色矩形,计算矩形与 UnCoveredRectangle 的交点。相交会导致以下情况之一:

    2.1 交点等于 UnCoveredRectangle。任务完成。

    2.2 交叉点从 CoveredRectangle 中“咬”出一个矩形块。剩余的 UnCoveredRectangle 将是矩形、L 形块或 U 形块。如果它本身是一个矩形,则将 UnCoveredRectangle 设置为剩余的矩形,然后转到步骤 2。如果 UnCoveredRectangle 是 L 形或 U 形,则将其拆分为 2 或 3 个矩形并从步骤 2 递归..

  3. 如果在 UnCoveredRectangle 的(部分)区域被发送到 0 之前用完红色矩形,那么您就完成了。

好的,我不知道这个算法的复杂性,但除非矩形的数量很大,否则我不太担心,尽管@den 可能是。而且我省略了很多细节。而且我不能像@den 那样画出漂亮的图表,所以你必须自己画出来。

【讨论】:

  • 如果不按位置对矩形进行排序,则会得到不同类型的结果:它可以是O(切为4),甚至可以是||,拆分为两个。排序也有帮助,因为您可以很早就知道矩形并没有完全覆盖该区域。
  • 至于复杂性 - 基本上,您将模型视为一个多边形(具有多个路径 - 它可能会被分割),然后减去每个矩形。多边形变得越来越复杂,所以我相信我们正在研究 O(N²)。反之亦然:您可以合并所有矩形,然后添加模型,看看是否有区别。
  • 比O(N^2)复杂
【解决方案9】:

这是一种不使用光栅化的方法,即仅使用纯数字。

注意:这不是 O(n log n),更像是 O(n^2)。然而,它是一个解决方案。是否是答案,如果 O(n log n) 是要求,则可能不是。

  1. 创建一个包含左右边缘的所有唯一 X 坐标的列表(在同一个列表中)
  2. 在构建此列表时,对于每个坐标,还要将一个矩形列表与其关联,并在此列表中表示与该列表关联的 X 坐标是矩形的左边缘还是右边缘
  3. 对坐标列表进行排序,使其从左到右升序
  4. 创建一个新的矩形列表,最初为空
  5. 遍历坐标列表,并为其处理相关的矩形列表
  6. 关联列表中所有以坐标为左边缘的矩形都应添加到您在 4 中创建的列表中。
  7. 应删除所有以坐标为右边缘的矩形。
  8. 添加和删除的顺序实际上应该以相反的顺序完成,首先要删除右边缘矩形,然后添加所有左边缘矩形
  9. 现在,在从 4 中创建的列表中删除矩形之前,您需要对其进行处理。您通过将它们视为“子矩形”来处理它们。它们的“新”左边缘坐标是您处理循环的上一次迭代(在 5 中)的 X 坐标,新的右边缘是正在处理的当前 X 坐标
  10. 算法的输出是一组坐标对(左右两个 X 坐标),每对都有一个相关联的矩形列表(垂直条)

因此输出应该是:

  1. X=1..2,矩形=A
  2. X=2..3,矩形=A,B
  3. X=3..4,矩形=A,B,C
  4. X=4..5,矩形=A,C
  5. X=5..6,矩形=C

让我来说明到目前为止的过程

+-------------------+
|A                  |
|        +----------+-----+
|        |C         |     |
|  +-----+----+     |     |
|  |B    |    |     |     |
|  |     +----+-----+-----+
|  |          |     |
+--+----------+-----+
   |          |
   +----------+

^  ^     ^    ^     ^     ^
1  2     3    4     5     6  <-- X-coordinates

相关矩形:

  1. 左:A,右:(无)
  2. 左:B,右:(无)
  3. 左:C,右:(无)
  4. 左:(无),右:B
  5. 左:(无),右:A
  6. 左:(无),右:C

您现在创建一个空列表L=[],并开始处理坐标和相关的矩形:

X=1

列表为空,无任何处理 没有什么可删除的 添加A:L=[A]

X=2

List 包含矩形,将列表处理为具有 X=1 左边缘和 X=2 右边缘的矩形(我们到目前为止处理的两个坐标),并使用它们的原始顶部和底部坐标。 没有什么可以删除的。 添加 B:L=[ A, B ]

X=3

List包含矩形,处理list(A和B)的方式相同,将它们视为暂时具有左右坐标为X=2和X=3,并使用它们原来的顶部和底部坐标。 没有什么可删除的 加 C: L=[ A, B, C ]

X=4

三个矩形的处理方法同上,临时左右坐标为X=3和X=4 删除 B:L=[A, C] 没什么可补充的

X=5 和 X=6

以完全相同的方式处理这些。

这意味着您最终会得到矩形的“条带”,就像这样(我将它们拉开一点以更清楚地说明它们是条带,但它们像在原始图中一样连续并排放置):

+--+ +-----+ +----+ ------+
|A | |     | |    | |     |
|  | |     | +----+ +-----+ +-----+
|  | |     | |C   | |     | |     |
|  | +-----| +----+ |     | |     |
|  | |B    | |    | |     | |     |
|  | |     | +----+ +-----| +-----+
|  | |     | |    | |     |
+--+ +-----+ +----+ +-----+
     |     | |    |
     +-----+ +----+
^  ^ ^     ^ ^    ^ ^     ^ ^     ^
1  2 2     3 3    4 4     5 5     6

好的,现在你有了输出,一组坐标对,每对都有一个相关的矩形列表。

现在我们做一个技巧。我们以完全相同的方式处理垂直条,只是这次我们使用 Y 坐标作为分隔符。让我们处理上面 3 到 4 之间的条带。请记住,条带的左右坐标分别为 3 和 4。

条看起来像这样:

^     +----+ <-- 1
A     |    |
|   ^ +----+ <-- 2
|   C |    |
| ^ | +----+ <-- 3
| B | |    |
| | V +----+ <-- 4
| |   |    |
V |   +----+ <-- 5
  |   |    |
  V   +----+ <-- 6

矩形坐标列表:

  1. 顶部=A,底部=(无)
  2. 顶部=C,底部=(无)
  3. 顶部=B,底部=(无)
  4. 顶部=(无),底部=C
  5. 顶部=(无),底部=A
  6. 顶部=(无),底部=B

新建空列表 L=[]

处理坐标:

Y=1

无需处理 (L=[]) 将 A 添加到列表中,L=[ A ]

Y=2

进程 A 的顶部和底部坐标暂时为 Y=1 和 2(请记住,它还具有临时的左右坐标 X=3 和 4 加 C, L=[ A, C ]

Y=3

进程 A 和 C,现在都具有 (3, 2)-(4, 3) 的临时坐标 加 B, L=[ A, B, C ]

Y=4

进程 A、B 和 C,坐标均为 (3, 3)-(4, 4) 删除 C, L=[ A, B ]

Y=5

进程 A 和 B,坐标 (3, 4)-(4, 5) 去掉A,L=[B]

Y=6

进程B,坐标(3, 5)-(4, 6)

最终输出:

成对的 Y 坐标,以及与之关联的矩形(也暂时获得了新的 X 坐标):

  • (3, 1)-(4, 2) - A
  • (3, 2)-(4, 3) - A, C
  • (3, 3)-(4, 4) - A, B, C
  • (3, 4)-(4, 5) - A, B​​li>
  • (3, 5)-(4, 6) - B

现在,假设您要问一个问题:B 是否被其他矩形的所有任意组合完全覆盖。

答案如下:

  1. 遍历上面最终列表中的所有矩形
  2. 如果 B 不是相关矩形的一部分,则忽略此矩形
  3. 如果有任何其他与坐标相关联的原始矩形,则B的这部分被那个/那些矩形覆盖
  4. 如果没有其他原始矩形与坐标相关联,则不覆盖 B 的这部分。

在上面的示例中,我们看到最终列表中的第 3 和第 4 个矩形包含 B,但也包含其他矩形,因此 B 的那些部分被覆盖,但列表中的最终矩形也包含 B,但没有其他矩形,因此这部分没有被覆盖。

根据原图,最终结果将包括如下矩形(每个内部的字母表示哪个原始矩形与这个新矩形相关联):

+--+-----+----+-----+
|A |A    |A   |A    |
|  |     +----+-----+-----+
|  |     |AC  |AC   |C    |
|  +-----+----+     |     |
|  |AB   |ABC |     |     |
|  |     +----+-----+-----+
|  |     |AB  |A    |
+--+-----+----+-----+
   |B    |B   |
   +-----+----+

如果我们现在重新看一下原始图表,我已经将上述算法会发现包含 B 但没有其他矩形的部分涂上阴影:

+-------------------+
|A                  |
|        +----------+-----+
|        |C         |     |
|  +-----+----+     |     |
|  |B    |    |     |     |
|  |     +----+-----+-----+
|  |          |     |
+--+-----+----+-----+
   |#####|####|
   +-----+----+

中间的垂直条用于说明该部件将作为两个矩形返回,并在该位置拆分,因为垂直条的计算方式。

我真的希望我在这里能理解自己。我有一些代码可以帮助您通过坐标列表执行每次运行,但现在是午夜 01:21,我要睡觉了,但是如果您希望看到一些实际代码,请发表评论.

或者自己实现它是一个很好的练习:)

这是包含相关方法的类的链接:RangeExtensions.cs

该方法是Slice 方法,只需搜索页面即可。有问题的类型Range,基本上是一个值到另一个值的范围,所以除了上面的算法,还有一点数据的构建和维护。

【讨论】:

  • 最终的“列表”不会是 N^2 的大小(O(N) 个垂直条,每个被划分 O(N) 次),因此是迭代查找孤立 B 的最后一步O(N^2)?
  • 首先你横向处理N个东西,然后向下处理N个东西,然后是最终结果,所以在截断之前是O(N^2+aN),所以是的,它是O( N^2)。我没有说它是 O(n log n),很抱歉没有提到它不是。这是*一个解决方案,一个相当快的解决方案,我有一个充满重叠矩形的表格,其中每个小部分的颜色与覆盖该部分的矩形数量有关,这是在我最初优化我的实现时构建的这个分区问题。我将发布处理此问题的代码的链接。
  • 我上面的答案与我对该问题的答案相同:stackoverflow.com/questions/244452/…,使用相同的代码。我的实现与链接的实现之间的区别在于,我的方法采用有序的范围源并生成切片,并且范围的源不必是内存列表,它可以是流源(就像从数据库中一样)。因此,为什么我不使用链接的“绘制单元格”实现,顺便说一句,这非常优雅。
【解决方案10】:

以下是如何在 O(n lg n) 中制作扫描线。我将重点介绍 BST 如何工作的棘手部分。

保持平衡的 BST,其中包含与当前扫描线相交的每个矩形的起点和终点。 BST 的每个节点都包含两个辅助字段:minOverlap 和 deltaOverlap。字段 minOverlap 通常存储与该节点的子树覆盖的区间中的任何点重叠的最小矩形数。但是,对于某些节点,该值略有偏差。我们维护一个不变量,即 minOverlap 加上直到根节点的每个节点的 deltaOverlap 之和具有与节点子树中的区域重叠的真正最小矩形数。

当我们插入一个矩形起始节点时,我们总是在叶子处插入(并且可能重新平衡)。当我们遍历插入路径时,我们将任何非零 deltaOverlap 值“下推”到插入节点的访问路径的子节点,更新访问路径上节点的 minOverlap。然后,我们需要在 O(lg n) 时间内将每个节点递增到树中插入节点的“右侧”。这是通过增加插入节点的所有右祖先的 minOverlap 字段并增加插入节点的右祖先的所有右子节点的 deltaOverlap 来实现的。对结束矩形的节点的插入以及点的删除执行类似的过程。可以通过仅修改轮换中涉及的节点的字段来执行重新平衡操作。您所要做的就是检查扫描中每个点的根,看看 minOverlap 是否为 0。

我省略了处理重复坐标之类的细节(一个简单的解决方案是在相同坐标的任何闭合矩形节点之前将开放矩形节点排序),但希望它能给你这个想法,并且是相当有说服力。

【讨论】:

    【解决方案11】:

    你知道the area of the union of rectangles 的最坏情况O(nlogn) 算法吗?

    你需要做的就是计算这两个区域:

    1. N 个矩形的面积
    2. N 个矩形和模型的面积

    如果这些面积相等,则模型被完全覆盖,否则不被覆盖。

    【讨论】:

    • 您的链接是一个算法,该算法计算出一组矩形的重叠(即交集)区域,而不是它们的联合
    【解决方案12】:

    这是一个使用一些内存的 O(n lg n) 运行时方法。

    使用示例:

    我们只对包含“模型”矩形的场景子部分感兴趣;在这个例子中,“模型”矩形是1,1 -&gt; 6,6

     1 2 3 4 5 6
    
    1 +---+---+
       | |
    2 + A +---+---+
       | |乙|
    3 + + +---+---+
       | | | | |
    4 +---+---+---+---+ +
                   | |
    5 + C +
                   | |
    6 +---+---+
    

    1) 将模型矩形边界内的所有 x 坐标(左右)收集到一个列表中,然后对其进行排序并删除重复项

    1 3 4 5 6

    2) 将模型矩形边界内的所有 y 坐标(顶部和底部)收集到一个列表中,然后对其进行排序并删除重复项

    1 2 3 4 6

    3) 通过唯一 x 坐标之间的间隙数 * 唯一 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

    【讨论】:

    • 我不明白这是怎么回事O(n log n):网格有大约O(n^2) 元素,你一个一个地填充它们。它基本上是一种光栅方法。
    • 这也是@Larry 写的
    猜你喜欢
    • 2015-11-06
    • 1970-01-01
    • 2020-07-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-21
    • 2023-04-08
    相关资源
    最近更新 更多