遍历线段

时间:2013-03-09 01:41:04

标签: algorithm data-structures

我对这个算法问题有疑问;我会粘贴问题,然后回顾我当前的想法和解决方案。

N (up to 100,000)个线段定义为[(x1, y1), (x2, y2)],其中x1 < x2y1 < y2(例如,线段具有正斜率)。即使在端点处,也没有线段接触或交叉。第一个细分有(x1, y1) = (0, 0)。想象一下,每个部分都是一个人必须攀爬的二维山。

一个人从(0, 0)开始,落在第一座山上。每当一个人降落在山坡上时,他爬到最后,(x2, y2)并直接向下跳。如果他降落在另一座山上(该段的任何地方),过程仍在继续:他爬上那座小山并跳跃。如果没有更多的山丘,他会落到-INFINITY并且过程结束。每个山(x1, y1) -> (x2, y2)都应该是 被视为包含点(x1, y1)但不包含点(x2, y2),以便如果他从上面落在上面,那么该人将落在山上 x = x1的位置,但如果他摔倒,他就不会降落在山上 来自x = x2的上方。

目标是计算他接触的山丘数量。

我目前的想法

我正在考虑沿着x轴扫过飞机的一条线。每个部分由BEGIN和END事件组成;每当我们遇到线段的开头时,我们将它添加到一个集合中。每次我们遇到线段的结尾时,我们都会从集合中删除它。当我们到达当前山丘的终点时,我们应该检查我们可以降落的最高山丘的集合。但是,我不知道如何快速确定如何检查,因为集合中可能有N个条目。此外,在跳到另一座山之后,这些的顺序会发生变化,因为每个段的斜率可能不同,我不知道如何解释这个差异。

有什么想法吗?

4 个答案:

答案 0 :(得分:1)

在预处理中,您可以遍历所有段并在stl multimap中添加点&lt;对,linesegment&gt;或类似的东西。这种预处理的成本是O(NlogN)。然后,您可以继续使用扫描线方法。您需要从multimap迭代点。因为所有点都是排序的,并且包含对应于该点的行的引用,所以它将花费O(N)。

答案 1 :(得分:1)

巴伦,你的算法是完全正确的。排序列表中元素的顺序不会随着扫描线的移动而改变,因为如果发生这种情况,您将会有一个交叉的线段。

您只需要一种方法来跟踪已排序的线段。实现此目的的一种方法是保留线段图,其中比较运算符将线段与段上的y值进行比较,该值由当前扫描位置的当前x值计算。从此映射插入,删除和查询是O(log(n))。

答案 2 :(得分:0)

我认为线扫描算法在这里是一个好主意。让我总结一下你的算法到目前为止并添加我的改进:

  • 你从左到右扫过一条线。
  • 您有一个列出所有当前活动细分的有效列表。 这些是与扫描线相交的段
  • 每个线段的每个端点都被视为“事件”
  • 当该行横扫段的“开始”时,该段将被添加到活动段列表中
  • 当线条扫过某个细分受众群的“结束”时,该细分受众群将从活动细分列表中删除
  • 如果删除线段后活动集中没有线段,则流程结束
  • 如果在删除线段时活动集中有线段,我们需要确定
    • A)活动集中是否有任何线段,其中部分“低于”先前删除的结束顶点,并且
    • B)这个人将落在哪个线段上。

我们的想法是在“活动集”中对您的线段进行排序,以使此查询更有效。我在想的是,如果我们知道一条线的斜率和y截距,我们可以计算起始顶点x位置的交点

GreaterThan(segment1,segment2){ // is segment 1 higher than segment 2?
//y = mx + b; compute y value of point on segment 2 for a given x value from s1
//that is, m and b are slope and y-intercept of s2
yVal = m * (segment1.first.x) + b
if (yVal < segment1.first.y)
   return true //the point on s2 corresponding to s1.first is lower than s1.first
return false
}

因为线不相交,所以你可以假设没有其他线会经过这条线。

如果我们避免添加其起始顶点高于我们“人”当前行的结束顶点的任何线段,那么我们应该成功避免将任何无关的线段添加到活动集(即“我们当前的”线段之上一个)

现在我们只需要担心最后一个线段顶点的特殊情况不是“可着陆”。因为顶点是事件,所以我们将在进行分段测试之前处理所有事件。这样,您就不会意外地落在活动集中一条线的末端顶点上,但是 WILL 将落在刚刚添加的线段上。

现在我们在活动集中有一个排序的线段列表,我们可以在恒定的时间内查询它以获得最高的一个,而添加一个新的只应该采用对数时间。

这听起来怎么样?

答案 3 :(得分:0)

这是Haskell的一个粗略方向。 “segment”是线段。 (在此示例中,第三个段稍微高于第二个段以测试代码。)“matches”查找将最后一个段的顶部pt(x0,y0)放在其x边界内的山/段并且高于或等于对应于它们的仿射变换x的y(“仿射”计算该段的仿射函数 - ax + b,可以这么说)。最后,countHills测试下一座山的可能匹配,并选择与y0最接近的y匹配(由仿射a * x0 + b计算),并输出结果,按顺序累积爬坡。显然,这个想法可能需要针对更长的细分列表进行优化。

下面的结果输出显示了第一个和第三个段。第二个山/段不在结果中,因为它低于第三个 - 我们落在第三个而不是:第三个 *主&GT; countHills段
[((0.0,0.0),(2.0,5.0)),((1.0,1.5),(5.0,3.0))]

import Data.List

segments = [((0,0),(2,5)),((1,1),(5,2)),((1,1.5),(5,3))]

top segment = snd segment

matches pt = 
  let x0 = fst pt 
      y0 = snd pt
  in filter (\x -> x0 >= fst (fst x) 
                   && x0 < fst (snd x) 
                   && (affine x) x0 <= y0) segments  

affine segment = 
  let x1 = fst $ fst segment
      y1 = snd $ fst segment
      x2 = fst $ snd segment
      y2 = snd $ snd segment
  in (+ ((x1*y2-x2*y1) / (x1-x2))) . (* ((y2-y1) / (x2-x1)))

countHills segments = countHills' (head segments) [] where
  countHills' x result = 
    let hills = matches $ top x
        x0 = fst (top x)
        y0 = snd (top x)
    in if null hills 
          then result ++ [x]
          else let nextHill = 
                     minimumBy (\a b -> compare 
                                        (y0 - (affine a) x0) 
                                        (y0 - (affine b) x0)) hills
               in countHills' nextHill (result ++ [x])