【问题标题】:Traversing Line Segments遍历线段
【发布时间】:2013-03-09 01:41:03
【问题描述】:

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

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 个条目。另外,跳到另一座山之后,这些的顺序会改变,因为每个路段的坡度可能不同,我不知道如何解释这种差异。

有什么想法吗?

【问题讨论】:

  • 没有人发布任何避免在每个事件中遍历O(n) 列表的内容。我正在密切关注这个问题。
  • 您能否链接到一些基准数据,以便我们尝试不同的优化?

标签: algorithm data-structures


【解决方案1】:

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

【讨论】:

    【解决方案2】:

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

    您只需要一种方法来跟踪已排序的线段。做到这一点的一种方法是保留线段图,其中比较运算符根据线段上的 y 值比较线段,该线段由当前扫描位置的当前 x 值计算得出。从此映射中插入、删除和查询是 O(log(n))。

    【讨论】:

      【解决方案3】:

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

      下面的结果输出显示了第一段和第三段。第二个山丘/路段不在结果中,因为它低于第三个 - 我们改为降落在第三个:

      *Main> 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])
      

      【讨论】:

        【解决方案4】:

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

        • 您正在从左到右扫过一条线。
        • 您有一个活动列表,其中列出了所有当前活动的段。 这些是与扫描线相交的线段
        • 每个线段的每个端点都被视为一个“事件”
        • 当线扫过段的“开始”时,该段将添加到活动段列表中
        • 当线扫过段的“末端”时,该段将从活动段列表中删除
        • 如果在移除线段后活动集中没有线段,则流程结束
        • 如果移除一条线段后激活集中有线段,我们需要确定
          • 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
        }
        

        因为线不相交,所以您可以假设没有其他线会“穿过”这条线。

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

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

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

        这听起来怎么样?

        【讨论】:

        • 这听起来像我所拥有的(在我发布这个之后),除了我按 Y 截距排序。 (以同样的理由:“你不能‘越过’一个部分”,因为没有交叉点)。我想 Y-intercept 和 Y1 都可以吗?
        • 我有一个反例:考虑这种情况:i.imgur.com/jY5rIXu.png 你的算法会跳到最右边的行,因为 y 坐标更大。
        • End Y 顶点听起来不错……我会再考虑一下。
        • 你能证明它为什么有效吗?我的直觉/逻辑表明,由于 Y 坐标较高,并且线条不交叉,因此低于该坐标的任何东西都没有更高的机会。
        • 还有:@SauceMaster。这是一个按结束 y 坐标排序的反例。它跳过中间行:i.imgur.com/m9ZRwwE.png
        猜你喜欢
        • 2019-08-09
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-02-27
        • 1970-01-01
        • 2012-09-17
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多