垂直能见度问题的高效算法


18

在思考一个问题时,我意识到我需要创建一种有效的算法来解决以下任务:

问题:我们给了n边的二维方盒,其边与轴平行。我们可以从顶部进行调查。但是,也有m水平段。每段具有的整数y -协调(0yn)和x坐标- (0x1<x2n)并连接点(x1,y)(x2,y)(看下图)。

我们想知道,对于框顶部的每个单元段,如果我们仔细观察该段,可以在框内垂直看到多深。

x{0,,n1}maxi: [x,x+1][x1,i,x2,i]yi

示例:给定和分段,如下图所示,结果为。看看有多深的光线可以进入盒子。= 7 5 5 5 3 8 3 7 8 7 n=9m=7(5,5,5,3,8,3,7,8,7)

七段  阴影部分表示光线可以到达的区域

对我们来说幸运的是,和都非常小,我们可以离线进行计算。nm

解决此问题的最简单算法是蛮力:对于每个段,遍历整个数组并在必要时进行更新。然而,它给我们不是很可观。O(mn)

很大的改进是使用了一个片段树,该树能够在查询过程中最大化片段上的值并读取最终值。我不会进一步描述它,但是我们看到时间复杂度是。O((m+n)logn)

但是,我想出了一个更快的算法:

大纲:

  1. 按坐标的降序对段进行排序(线性时间使用计数排序的变化)。现在注意,如果以前任何段都被任何段覆盖,则随后的任何段都不能再束缚通过该段的光束。然后,我们将从框的顶部到底部进行扫线。yxx

  2. 现在让我们介绍一些定义: -unit segment是扫描中的虚构水平线段,其坐标是整数且长度为1。在扫描过程中,每个线段都可能未标记(即,从框顶部可以到达此细分)或标记(相反的情况)。考虑一个单位段,其中,始终未标记。让我们还介绍集合。每组将包含一个连续的连续标记的单位片段(如果有)的完整序列,其后带有未标记的xxxx1=nx2=n+1S0={0},S1={1},,Sn={n} x 分割。

  3. 我们需要一个能够在这些段上运行并高效设置的数据结构。我们将使用由包含最大单位段索引(未标记段的索引)的字段扩展的find-union结构x

  4. 现在,我们可以有效地处理细分了。假设我们现在按顺序考虑第个分段(称为“查询”),该分段以开始,以结尾。我们需要找到所有包含在第个分段内的未标记分段(这些分段正是光束将在其上结束的分段)。我们将执行以下操作:首先,我们在查询中找到第一个未标记的段(找到其中包含的集合的代表,并获取该集合的最大索引,根据定义索引为未标记的段)。然后这个指数ix1x2 xix1X在查询内,将其添加到结果(此细分结果是)并标记这个索引(联盟含有集和)。然后重复此过程,直到找到所有未标记的段,即下一个“ 查找”查询给出索引。ÿXX+1个XX2

请注意,每个find-union操作仅在两种情况下完成:要么我们开始考虑一个段(可能发生次),要么我们刚刚标记了一个单位段(这可能发生次)。因此,总体复杂度为(是逆阿克曼函数)。如果不清楚,我可以详细说明。如果我有空的话,也许可以添加一些图片。x n O n + m α n αXñO((n+m)α(n))α

现在我到达了“墙”。尽管似乎应该有一个线性算法,但我无法提出线性算法。因此,我有两个问题:

  • 是否存在线性时间算法(即)来解决水平线段可见性问题?O(n+m)
  • 如果不是,那么可见性问题是的证明是什么?ω(n+m)

您对m个细分进行排序的速度有多快?
2014年

@babou,问题指定计数排序,正如问题所述,它以线性时间(“使用计数排序的线性时间”)运行。
DW

您是否尝试过从左向右扫动?您所需要的只是按照和步骤对和排序,以向右移动。因此总。x 2 O m O m O m x1x2O(m)O(m)O(m)
invalid_id 2014年

@invalid_id是的,我尝试过。但是,在这种情况下,扫掠线在遇到分段的开头时必须做出适当的反应(换句话说,将等于分段的数字乘以多集坐标),遇到分段的末尾(消除 -坐标)并输出最高的活动细分(在多组商品中输出最大值)。我还没有听说有任何数据结构可以让我们在(摊销后的)固定时间内执行此操作。ÿyy
mnbvmar 2014年

@mnbvmar可能是一个愚蠢的建议,但是大小为的数组如何扫掠并停止每个单元格 O n 。对于每个单元格,您都知道max y,可以将其输入矩阵中,此外,您还可以使用变量来跟踪总体最大值。nO(n)y
invalid_id 2014年

Answers:


1
  1. 首先对两个单独的数组AB中的线的x 2坐标进行排序。O x1x2ABO(m)
  2. 我们还保持辅助位阵列大小来跟踪活动段。n
  3. 开始从左向右扫动:
  4. 对于(i=0,i<n,i++)
  5. {
  6. ..如果ýÇ Ô 1 x1=iyc O(1)
  7. .. {
  8. .... find(max
  9. .... store(O 1 maxO(1)
  10. ..}
  11. ..如果ýÇ Ô 1 x2=iyc O(1)
  12. .. {
  13. .... find(max
  14. .... store(O 1 maxO(1)
  15. ..}
  16. }

可以使用具有n位的位数组来实现find()。现在,无论何时我们向L删除或添加元素,我们都可以通过分别将位设置为true或false来更新此整数。现在你有根据所使用的编程语言和假设两个选项Ñ相对较小,即小于ö Ñ ö Ñ Ñ 其是至少64位或这些整数的固定量:maxnLnlonglongint

  • 一些硬件和gcc支持在恒定时间内获取最低有效位。
  • 通过将转换为整数O 1 ),您将获得最大值(不是直接获得,而是可以导出)。LO(1)

我知道这是一个很烂的事情,因为它假定为最大值,因此n可以看作是一个常数。nn


依我之见,假设你有64位x86处理器,你只能处理。如果n约为数百万怎么办?n64n
mnbvmar 2014年

然后,您将需要更多的整数。使用两个整数,您最多可以处理 128,以此类推。因此,O m 最大查找步骤隐藏在所需的整数数量中,如果m较小,您可能仍会优化该整数。您在问题中提到n相对较小,因此我猜想n的数量级不大。顺便说一下,即使在32位处理器上,long long int根据定义也始终至少为64位。nO(m)mn
invalid_id 2014年

当然,C ++标准定义long long int为至少64位整数类型。但是,如果很大并且我们将单词的大小表示为w(通常w = 64),那不是每个数字都将取O nnww=64find的时间?那么我们将得出总OmnO(nw)O(mnw)
mnbvmar 2014年

是的,不幸的是,值很大。所以现在我想知道在您的情况下n将有多大以及它是否有界。如果这的确是数以百万计的顺序这个技巧,周围的人也会不再起作用,但如果Ç w ^ ñÇ值将是快速,实际上Ø ñ + 。因此,最佳算法选择通常取决于输入。例如,对于Ñ 100插入排序通常更快然后归并排序,即使使用的运行时间Ô Ñ 2相比nncwncO(n+m)n100O(n2)O(nlogn)
invalid_id 2014年

3
您选择的格式让我感到困惑。您知道您可以在此处排版代码,对吗?
拉斐尔

0

我没有线性算法,但是这个似乎是O(m log m)。

根据第一个坐标和高度对线段进行排序。这意味着每当x1 <x2时(x1,l1)总是在(x2,l2)之前。此外,只要y1> y2,则高度y1的(x1,l1)就会先于高度y2的(x1,l2)。

对于具有相同第一坐标的每个子集,我们执行以下操作。令第一段为(x1,L)。对于子集中的所有其他分段:如果该分段的长度大于第一个分段,则将其从(x1,xt)更改为(L,xt),然后以适当的顺序将其添加到L子集中。否则将其丢弃。最后,如果下一个子集的第一个坐标小于L,则将(x1,L)拆分为(x1,x2)和(x2,L)。以正确的顺序将(x2,L)添加到下一个子集。我们之所以可以这样做,是因为子集中的第一个段较高,并且覆盖(x1,L)的范围。这个新段可能是覆盖(L,x2)的段,但是直到我们查看具有第一个坐标L的子集,我们才知道这一点。

在遍历所有子集之后,我们将获得一组不重叠的段。要确定给定X的Y值是什么,我们只需要遍历其余部分即可。

那么这里的复杂性是什么:排序是O(m log m)。遍历子集为O(m)。查找也是O(m)。

因此,似乎该算法与n无关。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.