我对这个算法问题有疑问;我会粘贴问题,然后回顾我当前的想法和解决方案。
有N (up to 100,000)
个线段定义为[(x1, y1), (x2, y2)]
,其中x1 < x2
和y1 < 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个条目。此外,在跳到另一座山之后,这些的顺序会发生变化,因为每个段的斜率可能不同,我不知道如何解释这个差异。
有什么想法吗?
答案 0 :(得分:1)
在预处理中,您可以遍历所有段并在stl multimap中添加点&lt;对,linesegment&gt;或类似的东西。这种预处理的成本是O(NlogN)。然后,您可以继续使用扫描线方法。您需要从multimap迭代点。因为所有点都是排序的,并且包含对应于该点的行的引用,所以它将花费O(N)。
答案 1 :(得分:1)
巴伦,你的算法是完全正确的。排序列表中元素的顺序不会随着扫描线的移动而改变,因为如果发生这种情况,您将会有一个交叉的线段。
您只需要一种方法来跟踪已排序的线段。实现此目的的一种方法是保留线段图,其中比较运算符将线段与段上的y值进行比较,该值由当前扫描位置的当前x值计算。从此映射插入,删除和查询是O(log(n))。
答案 2 :(得分:0)
我认为线扫描算法在这里是一个好主意。让我总结一下你的算法到目前为止并添加我的改进:
我们的想法是在“活动集”中对您的线段进行排序,以使此查询更有效。我在想的是,如果我们知道一条线的斜率和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])