计算几何题目
如何判断一个点在多边形内部还是多边形外部?
射线法:
从一个点触发的射线,和一个多边形的相交次数如果是奇数,则在多边形内,否则在多边形外
1 | |
计算射线到圆的交点
此时仅需要计算t>0的时候的交点即可
1 | |
如何获取一群点的最小外接凸包
LeetCode 587. 安装栅栏 (Erect the Fence)
Andrew算法
将所有点按x坐标升序排序(x相同则按y升序)。
构建上下凸包:分两次遍历排序后的点,分别构建下凸包和上凸包。正向遍历时,构建下凸包时若当前点与栈顶两点形成非左转(叉积小于0),则弹出栈顶点,直到满足左转。
构建上凸包时,反向遍历,若当前点与栈顶两点形成非左转(叉积小于0),则弹出栈顶点,直到满足左转。
合并结果:将下凸包和上凸包合并,去除重复顶点(如首尾点),得到最终凸包。

先给出无序的合并,很简单,直接利用set
1 | |
如果是有序的合并呢?
回顾我们拥有的数据
我们有两个 vector,它们是按顺序构建的:
lower_hull:包含了从最左点到最右点的所有下凸包顶点(按 X 坐标递增)。
upper_hull:包含了从最右点到最左点的所有上凸包顶点(按 X 坐标递减)。
识别重复的点
lower_hull 和 upper_hull 共享了两个端点:
最左点:lower_hull[0] 和 upper_hull.back() 是同一个点。
最右点:lower_hull.back() 和 upper_hull[0] 是同一个点。
合并策略(生成逆时针链)
我们的目标是创建一个 res 向量,它从最左点开始,沿着“下栅栏”走到最右点,然后沿着“上栅栏”走回最左点。
1 | |
算法的证明
这个算法(也称为 Andrew’s Algorithm)的正确性证明,依赖于两个核心思想:
- 分治法 (Divide and Conquer):通过排序,我们把一个复杂的 2D 凸包问题,拆解成了两个更简单的一维(1D-like)问题:上凸包 (Upper Hull) 和 下凸包 (Lower Hull)。
- 凸性与转向 (Convexity and Turns):我们利用叉积来强制维持凸包的“凸”性。
1. 核心思想:排序即“分治”
我们首先对所有点按 X 坐标(X 相同则按 Y 坐标)排序。
- 最左点 (P_min) 和 最右点 (P_max) 一定是凸包的顶点。
- 凸包就是从
P_min到P_max的“上半部分”(上凸包),和从P_min到P_max的“下半部分”(下凸包)组成的。
证明的第一步:
如果我们能分别正确地找到“上凸包”和“下凸包”,那么将它们合并(并去除重复的 P_min 和 P_max)就一定能得到完整的凸包。
所以,问题被简化为:如何证明你的“扫描”过程能正确找到下凸包(和上凸包)?
2. 证明的关键:为什么你的“弹出”规则是正确的?
我们来分析“下凸包” (L-to-R) 和“上凸包” (R-to-L) 的构建过程。
两个循环都使用了相同的规则: if (cp < 0) { pop_back(); }
cp < 0意味着 右转 (Right Turn)。cp > 0意味着 左转 (Left Turn)。cp == 0意味着 共线 (Collinear)。
规则是:“只要形成右转,就弹出栈顶的点”。
情景一:构建 lower_hull (从左到右 L-to-R)
- 目标:我们要构建一个“下栅栏”,所有点都必须在这个栅栏的上方或线上。
- 特性:一个正确的“下栅栏”,从左到右看,它只能向左转或保持直线。
- 例子:
A=(0,0), B=(1,-1), C=(2,0)- 这是一个左转 (
cp > 0)。点B在A-C连线的下方。 - 这形成了一个“凹陷”,是“下栅栏”的有效部分。
- 你的代码:
cp > 0,不满足if (cp < 0),执行break。保留B。正确!
- 关键情况(右转):
A=(0,0), B=(1,1), C=(2,0)- 这是一个右转 (
cp < 0)。点B在A-C连线的上方。 B点违反了“下栅栏”的定义(它跑到了“栅栏”的上方)。B点不可能是下凸包的一部分。- 代码:
cp < 0,满足if (cp < 0)。弹出B。正确!
下凸包证明小结:
在 L-to-R 扫描中,
while (cp < 0) { pop_back() }这个规则,会系统性地移除所有“凸起”在下栅栏上方的点,只保留那些形成“左转”或“共线”的点。这精确地构建了下凸包。
情景二:构建 upper_hull (从右到左 R-to-L)
- 目标:我们要构建一个“上栅栏”,所有点都必须在这个栅栏的下方或线上。
- 特性:一个正确的“上栅栏”,从右到左看,它也只能向左转或保持直线(相对于 R-to-L 的路径)。
- 例子:
A=(4,2), B=(3,3), C=(2,2)vec_AB = [-1, 1],vec_BC = [-1, -1]- 这是一个左转 (
cp = (-1*-1) - (1*-1) = 2 > 0)。 - 点
B在A-C连线的上方。这是“上栅栏”的有效部分。 - 你的代码:
cp > 0,不满足if (cp < 0),执行break。保留B。正确!
- 关键情况(右转):
A=(4,2), B=(3,1), C=(2,2)vec_AB = [-1, -1],vec_BC = [-1, 1]- 这是一个右转 (
cp = (-1*1) - (-1*-1) = -2 < 0)。 - 点
B在A-C连线的下方。 B点违反了“上栅栏”的定义(它“凹陷”了下去)。B点不可能是上凸包的一部分。- 代码:
cp < 0,满足if (cp < 0)。弹出B。正确!
上凸包证明小结:
在 R-to-L 扫描中,
while (cp < 0) { pop_back() }这个规则,会系统性地移除所有“凹陷”在上栅栏下方的点,只保留那些形成“左转”或“共线”(相对于 R-to-L 路径)的点。这也精确地构建了上凸包。
3. 最终总结
- 排序正确地将问题分解为
lower_hull和upper_hull。 - 你的
while循环(“弹出右转”)是一个**“凸性强制器”** (Convexity Enforcer)。 - 它在 L-to-R 扫描中,强制所有点都在“下栅栏”的上方(或线上)。
- 它在 R-to-L 扫描中,强制所有点都在“上栅栏”的下方(或线上)。
我写的是什么?我怎么不记得我为啥写下面的东西了?

超平面
法矢:
平面过中点
因此超平面方程为
寻找两个凸集的距离
求对偶问题
对偶函数为
这个式子可以变形为
三个变量的影响相互独立,对于而言,如果,第一项一定能找到一个合适的使其最小,否则的话可以达到负无穷。
因此
可以得到对偶问题为