N个矩形的并集周长

时间:2017-02-11 01:03:43

标签: algorithm data-structures segment-tree

我想知道解决这个问题的有效方法:

  

给定左上角和右下角的N个矩形,请找到N个矩形的并集周长。


我只有O(N^2)算法而且速度太慢,所以请找到更有效的算法。

您可以假设坐标值是正整数且小于100000。

编辑: 例如,在这种情况下,周长是30.

Example

O(n ^ 2)算法:

for x=0 to maxx
   for i=0 to N
      if lowx[i] = x
         for j=lowy[i] to highy[i]
            d[j]++
            if d[j] = 1 then ret++
      if highy[i] = x
         for j=lowy[i] to highy[i]
            d[j]--
            if d[j] = 0 then ret++
   for y=0 to maxy
      if d[y] = 0 && d[y + 1] >= 1 then ret++
      if d[y] >= 1 && d[y + 1] = 0 then ret++

最后的回答是答案。

3 个答案:

答案 0 :(得分:1)

一方面,这个问题的经典解决方案是基于扫描线的“布尔合并”算法,该算法以其原始形式构建这些矩形的并集,即构建结果的多边形边界。可以轻松修改算法以计算结果边界的周长,而无需在物理上构建它。

另一方面,基于扫描线的“布尔合并”可以为任意多边形输入执行此操作。鉴于在您的情况下输入受到更多限制(和简化) - 只是一堆同构矩形 - 很可能存在更轻量级和更聪明的解决方案。

注意,顺便说一下,这种矩形的联合实际上可能是一个多连通的多边形,即一个有洞的区域。

答案 1 :(得分:1)

在某些情况下,这可能会有所帮助:

考虑一下,

 _______
|       |_
|         |
|        _|
|___    |
    |   |
    |___|

的周长与此相同:

 _________
|         |
|         |
|         |
|         |
|         |
|_________|

答案 2 :(得分:1)

有一个O(n log n)时间扫描线算法。应用以下步骤来计算形状的垂直周长。转置输入并再次应用它们来计算水平周长。

对于每个矩形,准备一个由左侧x坐标键入的起始事件,其值为y-间隔,以及由右侧x坐标键入的停止事件,其值为y-间隔。按x坐标对这些事件进行排序,并按顺序处理它们。在任何时候,我们都保持一个数据结构,能够报告边界与扫描线相交的点数。在事件点之间的2n - 1个间隔,我们将该数字乘以间隔宽度到周长。

我们需要的数据结构在时间O(log n)中支持以下操作。

insert(ymin, ymax) -- inserts the interval [ymin, ymax] into the data structure
delete(ymin, ymax) -- deletes the interval [ymin, ymax] from the data structure
perimeter() -- returns the perimeter of the 1D union of the contained intervals

由于输入坐标是有界整数,因此可以通过segment tree实现一种可能的实现。 (实际输入有一个扩展,包括对输入的y坐标进行排序并将它们重新映射为小整数。)每个段都有一些相关的数据

struct {
    int covers_segment;
    bool covers_lower;
    int interior_perimeter;
    bool covers_upper;
};

其范围是输入间隔中存在的从其下降的段的并集。 (请注意,非常长的段对树的最底层没有影响。)

covers_segment的含义是它是分解中具有该段的区间数。 covers_lower的含义是,如果其中一个具有相同下端点的段之一属于某个区间的分解,那么它就是真的。 interior_perimeter的含义是范围内段的1D周长(如上所述)。 covers_upper的含义类似于covers_lower,具有上端点。

这是一个例子。

0 1 2 3 4 5 6 7 8 9

[---A---]
    [---B---] [-D-]
      [-C-]

间隔时间为A ([0, 4])B ([2, 4], [4, 6])以及C [3, 4] [4, 5]D [7, 8] [8, 9]

               c_s  c_l  i_p  c_u
[0, 1]          0    F    0    F
  [0, 2]        0    F    0    F
[1, 2]          0    F    0    F
    [0, 4]      1    T    0    T
[2, 3]          0    F    0    F
  [2, 4]        1    T    1    T
[3, 4]          1    T    0    T
      [0, 8]    0    T    2    F
[4, 5]          1    T    0    T
  [4, 6]        1    T    1    T
[5, 6]          0    F    0    F
    [4, 8]      0    T    2    F
[6, 7]          0    F    0    F
  [6, 8]        0    F    1    F
[7, 8]          1    T    0    T
        [0, 9]  0    T    2    T
[8, 9]          1    T    0    T

要插入(删除)间隔,请通过递增(递减)covers_segment来插入(删除)其组成段。然后,对于受影响的段的所有祖先,重新计算其他字段,如下所示。

if s.covers_segment == 0:
    s.covers_lower = s.lower_child.covers_lower
    s.interior_perimeter =
        s.lower_child.interior_perimeter +
        (1 if s.lower_child.covers_upper != s.upper_child.covers_lower else 0) +
        s.upper_child.interior_perimeter
    s.covers_upper = s.upper_child.covers_upper
else:
    s.covers_lower = true
    s.interior_perimeter = 0
    s.covers_upper = true

要实施perimeter,请返回

(1 if root.covers_lower else 0) +
root.interior_perimeter +
(1 if root.covers_upper else 0)

其中root是段树的根。