凸壳算法 - 格雷厄姆扫描最快的比较功能?

Posted

技术标签:

【中文标题】凸壳算法 - 格雷厄姆扫描最快的比较功能?【英文标题】:Convex Hull Algorithm - Graham scan fastest compare function? 【发布时间】:2017-01-12 22:05:50 【问题描述】:

我已经实现了 Graham 扫描,但我发现我的程序的瓶颈是排序(80% 的时间)。我想改进它,现在我正在做以下事情:

 std::sort(intersections.begin() + 1, intersections.end(), [&minElement](Point const& a, Point const& b)
 return angle (minElement - a, XAxis) < angle (minElement - b,XAxis););

这给了我准确的角度,但它并不便宜,因为我的角度函数如下所示:

float angle (const Point& v1, const Point& v2) 
    return dot (v1, v2) / (v1.Length () * v2.Length ());

在 Length 函数中,我必须做一个平方根,这是最昂贵的操作之一。但这样我得到了一个很好的订购。

我尝试按 Slope、dot、ccw 对数组进行排序,甚至只从比较中删除 sqrt,但这些都没有为我提供相同的排序。你能给我什么建议吗?

【问题讨论】:

不是答案,但可以用单调链代替格雷厄姆扫描吗?我的意思是,单调链中的排序是通过坐标而不是角度来完成的,这样会快一点。 【参考方案1】:

当您按它们的相对角度对点进行排序时,您不需要知道两个点形成的确切角度。相反,您只需要知道一个点是在另一个点的左边还是在另一个点的右边。

图片,比如你要比较两个点(x1, y1)和(x2, y2),假设最底点在 (xp, yp)。看两个向量 v1 = (x1 - xp, y1 - y p) 和 v2 = (x2 - xp, y2 - yp)。要确定 v1 是在 v2 的左侧还是右侧,这意味着您要查看从 v 角度的符号1 到 v2。如果是正数,那么v2在v1的左边,如果是负数,那么v1在v的左边2.

二维叉积有一个很好的性质

v1 × v2 = |v1| |v2|正弦(θ)

其中 θ 是从 v1 到 v2 所形成的角度。这意味着如果 v1 位于 v2 的右侧,则 θ > 0,反之亦然,这很好,因为这可以让您比较哪一个纯粹通过取叉积!

换句话说:

如果v1 × v2 > 0,则v2在v1的左边. 如果 v1 × v2 = 0,则这些点共线。 如果 v1 × v22 在 v1 的右侧.

二维叉积公式由下式给出

(Δx1, Δy1) × (Δx2, Δy2) = (Δx1 Δy2 - Δx2 Δy1)

这里,Δx1代表x1 - xp

因此您可以计算上述数量,然后查看其符号以确定两点之间的关系。不需要平方根!

【讨论】:

是的,我试过了,我唯一错过的是我的函数返回的是整数,而不是浮点数。所以谢谢。 (我正在使用浮点数)另外,如果角度相同,但叉积不一样怎么办? 你能澄清你关于角度的问题吗?我没听懂。 @templatetypedef 但我认为 OP 需要排序函数来对所有点与 x 轴形成的角度进行排序。对于那个 sin 不起作用,因为它不是 [0, pi] 弧度的单调。那是他在计算角度时使用cosine函数。【参考方案2】:

您可以通过缓存进行临时优化。

首先,Length() 可以将其结果缓存在成员变量中。您只需要在点更改的情况下使该值无效(可能并非如此)。您可以进行惰性计算,因此 Length 将检查它是否有值,如果没有则计算/存储,然后返回存储的值。

其次,对于给定的 minElement 和 xAxis,angle (minElement - a, XAxis) 成为 1 个参数的函数,因此您可以在排序之前为每个点保存(缓存)它的值并使用比较器关于准备好的值。

处理这些事情的简单方法:在主算法中使用 Point 的子类,因此每个点都已经有必要的方法和缓存值的位置。

【讨论】:

以上是关于凸壳算法 - 格雷厄姆扫描最快的比较功能?的主要内容,如果未能解决你的问题,请参考以下文章

格雷厄姆扫描 C++ 不起作用

凸包中的额外点(使用格雷厄姆扫描)错误+ java

格雷厄姆扫描问题

亚线性但简单的动态凸壳算法?

youcans 的 OpenCV 例程200篇126. 形态算法之凸壳(Convex hull)

区间扫描线算法