想象你站在一间漆黑的展厅中央,手里举着一盏探照灯。灯只能照亮一堵墙的某一侧,而你想找遍整个展厅,找到一个“无论转移到哪里,都能同时被所有墙照亮”的位置——也就是站在这片区域里的任何一点,都能清楚地看清每一面墙上的展品。这个“安全区”就是多边形的核。而寻找核的过程,其实是一场不断调整射灯角度、用一堵墙一堵墙来“切割”展厅的拼图游戏。每加入一面墙,视野就收窄一点;最终留下来的一片公共亮区,就是答案。这就是半平面交(Half-Plane Intersection)的核心思想,也是我们今天这篇文章的主角。 半平面交要做的事情很简单:给你一堆“只允许站在这一侧”的约束,算出所有约束同时成立的那片区域。多边形核判定就是它最经典的用例——如果一个多边形的核非空,我们就说它是星形的;而核本身也可以用来做可视化裁剪、视点探测、路径规划等等。本文将把你从“射灯与墙”的直觉带向严谨的几何计算:如何用有向直线刻画一个半平面,如何按极角排序后逐条插入,如何用一个双端队列清理掉被新墙淘汰的旧边界,最终在 O(n log n) 时间内交出答案。我们会用大量图示、代码与实例,让你既懂原理,也会实现。
对 AI 从业者而言,计算几何是计算机视觉、机器人导航与图形可视化的底层数学工具;而半平面交作为其中经典算法,能帮你解决从“安全区域判定”到“视锥裁剪”的一类实际问题,构建扎实的算法功底。
可视化演示
半平面交计算多个半平面的公共区域,可用于多边形核判定与可视化裁剪。本文详解有向直线的半平面表示、极角排序增量法... 切换模式查看不同维度。
本节配有交互动效(进入视口后自动加载,约 3KB)。
半平面交核心:核心原理 动态展示关键流程与数据流动 交互控件调节参数实时渲染 f(x) = transform(x) 核心概念的可视化呈现
半平面概念
想象展厅里的第一面墙:它是一堵无限延伸的直墙,探照灯站在墙的某一侧,灯光只能照亮这堵墙的其中一半世界。这一半,在数学里就叫“半平面”。我们不需要真的把墙画出来,只需要给它一个方向——一条有向直线,箭头指向被照亮的那一侧,就能完整描述“允许站立的位置”。这就是半平面表示的核心:用一条直线的方向,定义“哪一边是亮的”。
具体来说,一条直线可以写成 \( ax + by + c = 0 \)。向量 \((a, b)\) 就是这条直线的法向量,它垂直指向墙的一侧。我们约定:法向量指向的那一侧就是“照亮的一侧”,也就是半平面。因此一个半平面可以写成关于 \( x, y \) 的一个不等式:
\[
H = \{(x, y) \mid ax + by + c \ge 0\}
\]
这里的 “\(\ge 0\)“ 表示包含边界本身——就像你站在墙根的线上也算在展厅内。如果换成 ”\(>0\)“,那就是不包含边界的”开半平面”。多数算法默认使用闭半平面,因为边界交点的计算更整齐。
1. 直线方程:\( ax + by + c = 0 \) 是一条无限长直线,所有满足等式的点 \((x,y)\) 都落在墙上。
2. 法向量方向:\((a,b)\) 是直线的法向量,它垂直指向被照亮的一侧,所以它决定了“哪边是亮的”。
3. 不等式范围:把等式改为 \(\ge 0\),意味着我们把墙的一侧连同墙本身一起包含进来,形成一个闭半平面。
回到展厅:每面墙都不再是“一堵实物”,而是一道有方向的光束指令。当你把所有墙的“照亮侧”叠加在一起时,公共亮区就在这些指令中浮现——这就是半平面交的起点。理解到这里,你已经掌握了第一个工具:用不等式描述几何区域。
多边形核定义
现在展厅不再是空旷的矩形,而是一个不规则的多边形房间,比如一个五角形展厅。你想找一个位置,从这个位置看向任意一面墙时,视线都不会被另一面墙挡住——换句话说,这个位置能“同时看到所有墙”。所有满足这个条件的位置组成的区域,就叫多边形的核。
核的数学定义非常优雅:把多边形的每条边都看成一面有方向的墙,法向量指向多边形内部,那么核就是所有这些半平面的交集。如果这个交集不是空的,我们就说这个多边形是星形的——像一颗星星,从核里任意一点都能“看到”整个多边形的轮廓。
\[
K(P) = \bigcap_{i=0}^{n-1} H_i
\]
其中 \(H_i\) 是第 \(i\) 条边对应的内部半平面。
1. 每条边的半平面:将多边形的每条边视为有向直线,左侧(内法向量侧)形成半平面 \(H_i\)。
2. 交集运算:把 \(n\) 个半平面做交集,交集里的点必须同时满足所有不等式。
3. 星形判定:若 \(K(P) \neq \varnothing\),则多边形为星形,核中任意点都满足全局可见性。
用射灯的语言来说:核就是那个“万能观赏位”。你不需要移动到房间的每个角落去试验,只需要求解这个交集——一次几何计算,就回答了“是否存在这样的位置”。这也解释了为什么核判定在游戏关卡设计、监控摄像头布点中如此重要:它直接告诉你,一个区域是否有一个“全览视角”。
极角排序增量法
终于到了核心算法:怎么高效地求出这些半平面的交集?如果盲目地把半平面两两求交,复杂度会爆炸。聪明的做法是像整理书架一样,先把所有墙按“朝向”排好队,再一面一面地“砌”进去。这个方法叫“极角排序增量法”。
第一步,把每个半平面的法向量 \((a,b)\) 按角度排序。法向量的角度就是探照灯的光轴方向,我们用 \(\text{atan2}(b, a)\) 来计算。排序之后,所有墙按方向绕了一圈,就像时针扫过表盘。第二步,维护一个双端队列(deque),队列里保存的是“当前仍然有效的边界半平面”以及它们两两之间的交点。每加入一面新墙,我们检查队列头部和尾部的交点是否还落在新墙的照亮侧——如果不在,说明这个交点已经被新墙淘汰,就把它从队列里弹出。最后,队列里剩下的就是所有半平面的交集边界。
\[
\theta_i = \text{atan2}(b_i, a_i) \quad \text{排序后:} \theta_0 \le \theta_1 \le \cdots \le \theta_{n-1}
\]
新半平面加入时,若队尾交点 \(q\) 满足 \(a_k q_x + b_k q_y + c_k < 0\),则弹出队尾。
1. 极角计算:每个半平面的法向量都有一个极角,用 \(\text{atan2}\) 计算,它的值域是 \((-π, π]\)。
2. 排序保证:排序后墙面按角度依次排列,增量插入时保证处理的顺序是“绕一圈”的完整性。
3. 淘汰条件:新墙加入时,若队列末尾的交点已经落入新墙的“暗侧”(不等式小于零),说明这个交点不再属于公共区域。
这个算法的妙处在于:每面墙只需要入队一次、出队一次,总共 O(n) 次操作。排序是唯一的“大开销”,这让整个算法达到 O(n log n)。队列就像一条传送带,墙体按角度有序流过,不符合条件的交点自动掉落,留下的就是核的轮廓。
复杂度分析
我们常说半平面交是 O(n log n) 的算法,这个复杂度从何而来?把它拆开看,只有两步:排序和扫描。
排序阶段,我们把 n 个半平面按法向量极角排序,最好的通用比较排序是 O(n log n)。这也是整个算法的瓶颈,因为后面的扫描过程是线性的。扫描阶段,我们用一个双端队列依次处理排好序的半平面:每个半平面最多被加入队列一次,也最多被弹出一次,所以均摊下来每个元素的操作是常数时间的,总共 O(n)。两部分相加,整体复杂度就是 O(n log n)。空间上,我们只需要存储排序后的数组和双端队列,都是 O(n)。
\[
T(n) = O(n \log n) + O(n) = O(n \log n), \quad S(n) = O(n)
\]
1. 排序开销:对 n 个半平面按极角排序,比较排序的下界是 n log n。
2. 线性扫描:双端队列中每个半平面最多入队、出队一次,均摊 O(1),总耗时 O(n)。
3. 合并结论:总复杂度取最大的量级,因此 \(O(n \log n)\) 主导了算法。
如果你面对的是十万条边的复杂多边形,排序的 n log n 和暴力两两求交的 n² 之间,差距是巨大的——前者在眨眼间完成,后者可能要跑到地老天荒。这就是算法设计的价值:把看似不可能的任务,压缩到可接受的时间窗口内。
关键实现细节
理论讲得再漂亮,落地写代码时总有细节绊脚。半平面交实现中有三个关键细节:求交点、判左右、处理退化。
首先是求交点。两个半平面的边界直线 \(L_1, L_2\) 相交,我们需要精确算出入射点。最稳健的方法是用参数化直线和叉积。设直线 \(L_1\) 过点 \(P_0\)、方向向量 \(d_1\),直线 \(L_2\) 过点 \(Q_0\)、方向向量 \(d_2\),则交点参数可由两个叉积的比值给出:
\[
t = \frac{\text{cross}(P_0 - Q_0, d_2)}{\text{cross}(d_2, d_1)}
\]
其中 \(\text{cross}(u, v) = u_x v_y - u_y v_x\)。第二个细节是“点在直线的哪一侧”:用叉积 \(s = \text{cross}(d, p - P_0)\),正数表示点在左(内部),负数表示在右(外部)。第三个细节是退化的处理:当两条直线的方向向量平行时,叉积分母接近零,这时需要特殊处理——比如直接跳过相同的半平面,或者用不等式判断谁更严格。
1. 参数化:把每条直线写成“起点 + 方向 × 参数”的形式,交点就是两条参数直线相等的点。
2. 叉积求参:利用二维叉积的性质,交点参数 \(t\) 等于两个叉积的比值,分母不为零时有效。
3. 侧向判定:计算 \(s = \text{cross}(d_1, p - P_0)\),\(s > 0\) 表示点在直线左侧(内部),\(s < 0\) 在右侧。
这些细节决定了算法的鲁棒性。如果忽略平行退化,程序会在除零时崩溃;如果忽略浮点误差,边界判断会抖动。好的实现就像一位细心的工匠,既懂原理,也懂得在微小的误差面前保持清醒。
工程应用场景
理论总归要落地。半平面交在实际工程中有一大批应用场景,这里举三个典型的。
第一个是可视化裁剪。在游戏引擎或地理信息系统中,摄像机有一个视锥体,场景里的物体只有在视锥体内才需要被渲染。视锥体在二维平面上可以被表示为若干个半平面的交集——一个矩形视口就是四个半平面。用半平面交判断一个多边形是否与视锥相交,可以快速剔除视野外的物体,节省大量渲染计算。第二个是核判定应用。在建筑设计中,判断一个房间是否“无死角”(任意位置都能看到安全出口),本质就是计算多边形的核是否覆盖出口位置。第三个是导航安全区域。机器人或自动驾驶车辆在规划路径时,需要计算可行走区域,这个区域常常被描述为多个障碍物的半平面约束的交集。
\[
p \text{ 可见} \iff \forall i,\; n_i \cdot p + c_i \ge 0
\]
其中 \(n_i, c_i\) 是第 \(i\) 个裁剪半平面的法向量和常数项。
1. 可见性判定:一个点 \(p\) 可见,当且仅当它满足所有裁剪半平面的不等式。
2. 视锥建模:视锥体的每个侧面都对应一个半平面,六个面构成六半平面交集。
3. 快速剔除:若物体包围盒与 \(V\) 无交集,则跳过该物体的渲染。
回到射灯的比喻:视锥裁剪就是剧场里只亮一盏追光灯,灯光外的区域自动“隐身”。半平面交让这种隐身判断变得既科学又高效——每条光束都是一条不等式,而“是否被照亮”就是一次代入计算。
常见陷阱处理
写半平面交代码时,最让人头疼的不是主流程,而是一堆“刁钻”的边界情况。这里把最常见的陷阱集中处理。
第一个陷阱是平行退化。当两条直线的方向向量平行时,叉积分母为零,交点公式失效。此时需要判断这两条平行线是否冗余:如果它们方向相同,保留更严格的那个(即约束更强的半平面);如果方向相反,则交集必为空,可以提前退出。第二个陷阱是重复半平面。输入中可能存在完全相同的半平面,如果不先去重,排序后相邻的重复项会导致队列中产生长度为 0 的边,破坏交点计算。第三个陷阱是浮点误差。几何计算中,叉积的结果可能非常接近零但并不精确等于零,因此判断“是否平行”和“点在不在边界上”时,需要使用一个极小阈值 \(\varepsilon\) 来容忍误差。
\[
|\text{cross}(d_1, d_2)| < \varepsilon \Rightarrow \text{判定为平行}
\]
\[
s = \text{cross}(d_1, p - P_0), \quad |s| \le \varepsilon \Rightarrow p \text{ 在边界上}
\]
1. 平行判定:当叉积绝对值小于阈值 \(\varepsilon\) 时,认为两条直线平行。
2. 边界容忍:点在直线上的判定也采用阈值,避免浮点噪声导致误判。
3. 去重与剪枝:排序前去重相同的半平面;发现反向平行时直接判定交集为空,提前终止。
这些细节才是工程实现与玩具代码的分水岭。正如探照灯也需要校准焦距,几何算法同样需要一套“误差处理机制”,才能在真实世界的噪声中稳定工作。
AI 应用场景
场景一:三维引擎视锥裁剪加速
阶段:数据预处理 / 渲染管线的早期粗粒度剔除
解决的问题:在三维渲染中,场景可能包含数百万个多边形,如果全部送入GPU渲染,性能会急剧下降。传统方式依赖CPU逐个物体与视锥体的空间关系判断,速度慢且复杂。利用半平面交,视锥体被建模为六个半平面,物体的包围盒与视锥体的交集判断被简化为不等式组的求交,可快速剔除视野外的物体。
具体案例:在自动驾驶仿真系统中,车载AI需要实时渲染周围道路环境。每帧渲染前,系统用半平面交将视野外的高精度建筑物模型快速剔除,渲染耗时降低约40%,保证仿真画面的流畅性。这一过程完全基于本文所述的“六半平面交集”模型,并以多线程并行执行。
场景二:机器人导航安全区域规划
阶段:路径规划中的可行域构造
解决的问题:机器人在仓库中移动时,需要知道哪些区域是“安全的”——既不撞墙,又能看到出口。传统做法是离散化地图做栅格膨胀,计算量大且不精确。用半平面交,将仓库墙壁建模为有向直线,内法向量约束机器人必须站在墙的内侧;多个半平面的交集就是安全区域。
具体案例:某仓储机器人团队用多边形核判定算法,为每台机器人实时计算“最大安全活动区”。当地图更新(新货架加入)时,只需重新做一次半平面交,即可获得新的安全区域,路径规划模块只在这一区域内搜索路径,规划耗时从毫秒级降到微秒级。
场景三:编译器前端可视化中的代码块裁剪
阶段:编译器前端可视化组件的几何布局
解决的问题:现代IDE在展示词法分析状态机或语法分析树时,需要将复杂的节点图布局在有限窗口中。传统方法按节点层级暴力绘制,节点重叠、超出边界时处理繁琐。利用半平面交,可以将“窗口可见区域”建模为四个半平面的交集,对每个节点做可见性判定:节点包围盒与窗口区域无交集则跳过绘制。
具体案例:某编译器教学平台开发了“词法分析可视化”功能,学生输入正则表达式后,平台自动生成NFA/DFA状态图,并在窗口中用半平面交裁剪出可视区域,状态转移图实时平滑缩放,极大地提升了教学体验。
Python 演示
import math class Point: def __init__(self, x, y): self.x = x self.y = y def __sub__(self, o): return Point(self.x - o.x, self.y - o.y) class Line: # 半平面: 点 p 满足 cross(dir, p - p0) >= 0 的一侧 def __init__(self, p0, p1): self.p0 = p0 self.dir = p1 - p0 # 方向向量 self.ang = math.atan2(self.dir.y, self.dir.x) def side(self, p): return cross(self.dir, p - self.p0) def cross(u, v): return u.x * v.y - u.y * v.x def intersect(l1, l2): # 两直线交点 (l1 参数 t, l2 参数 u) d1, d2 = l1.dir, l2.dir denom = cross(d2, d1) t = cross(l1.p0 - l2.p0, d2) / denom return Point(l1.p0.x + t * d1.x, l1.p0.y + t * d1.y) def half_plane_intersection(lines): # 1. 极角排序 + 去除平行冗余 lines.sort(key=lambda l: l.ang) unique = [] for l in lines: if not unique or abs(l.ang - unique[-1].ang) > 1e-9: unique.append(l) else: # 同向平行: 保留更严格(起点在对方内部)的那条 if unique[-1].side(l.p0) < -1e-9: unique[-1] = l # 2. 双端队列维护 dq = [] for l in unique: while len(dq) >= 2 and dq[-1][0].side(dq[-1][1]) < -1e-9: dq.pop() while len(dq) >= 2 and dq[0][0].side(dq[0][1]) < -1e-9: dq.pop(0) if dq: dq.append((l, intersect(dq[-1][0], l))) else: dq.append((l, None)) # 3. 收尾: 首尾相交验证 n = len(dq) if n >= 3: head = dq[0][0] if head.side(dq[-1][1]) < -1e-9: return [] dq.append((head, intersect(dq[-1][0], head))) dq = dq[:-1] # 去掉多出的边界 return [pt for (_, pt) in dq if pt is not None] if __name__ == "__main__": # 正五边形作为输入的"墙" N = 5 poly = [] for i in range(N): ang = -math.pi/2 + i * 2 * math.pi / N poly.append(Point(10 * math.cos(ang), 10 * math.sin(ang))) # 每条边 -> 内法向量侧的半平面 lines = [] for i in range(N): p0 = poly[i] p1 = poly[(i + 1) % N] # 保证内部在左侧: 逆时针顶点顺序 lines.append(Line(p0, p1)) core_pts = half_plane_intersection(lines) if core_pts: print("多边形核的顶点坐标:") for pt in core_pts: print(" ({:.4f}, {:.4f})".format(pt.x, pt.y)) print("核存在,顶点数 =", len(core_pts)) else: print("核为空: 该多边形不是星形多边形")
lines.sort(key=lambda l: l.ang)对所有半平面按法向量极角排序,这是 O(n log n) 复杂度的来源。if abs(l.ang - unique[-1].ang) > 1e-9用阈值去重平行且角度相同的半平面。if unique[-1].side(l.p0) < -1e-9同向平行时比较起点,保留约束更严格的半平面。while ... dq[-1][0].side(dq[-1][1]) < -1e-9新墙加入后,不断从队尾弹出被淘汰的交点。while ... dq[0][0].side(dq[0][1]) < -1e-9循环结束时,还要检查队首交点的合法性。intersect(l1, l2)利用叉积求两直线交点,分母为叉积,正是正文中的参数化公式。
常见误区
误区我总觉得 “\(ax+by+c \ge 0\)“ 表示的是”上方的区域“,或者”x 更大的区域”。写代码时经常把半平面的方向搞反,导致核算出来是空的。
正解半平面的方向完全由法向量 \((a, b)\) 决定,而不是直觉中的“上下”或“左右”。法向量指向哪边,哪边就是 \(\ge 0\) 的那一侧。就像探照灯,灯头朝向决定照亮哪边,而不是墙的位置决定。解决方法是画一条线和一个箭头来验证:代入一个点 \((0,0)\) 到不等式,如果满足,说明原点在照亮侧,这就是一个快速自检。
为何常见:人脑习惯用“上下左右”思考,而几何世界用“法向量的方向”思考。初学者没有建立“法向量指向内部”的直觉,就容易把方向弄反。
误区我以为核就是多边形的“重心”或者“中心点”,算出来一个点就够了。但看到核是一个多边形区域时,我以为是代码出错了。
正解核是一个区域,不是单个点。它表示所有“能同时看到所有边”的位置的集合。对于正五边形,核是一个小的凸五边形;对于某些凹多边形,核可能是一条线、一个点,甚至是不存在的。核作为“区域”存在,是因为可见性约束是连续的——稍微移动一点,只要还在所有半平面内,依然可见。
为何常见:“中心”是我们日常生活中最自然的“全能视角”直觉,但几何上的约束是线性的、连续的区域,而不是一个孤立的点。
误区我直接写交点公式 `t = cross(...) / cross(...)`,结果遇到两条平行边时程序直接除以零崩溃了。我以为是自己输入数据的问题,而不是算法需要处理退化。
正解平行线是半平面交中必然要处理的边界情况。当分母叉积接近零时,说明两直线平行,此时没有唯一交点,需要判断这两条线是冗余还是矛盾:同向平行保留更严格的,反向平行直接返回空集。工程实现中必须加一个阈值判断,就像正文中写的 abs(cross(d1,d2)) < eps。
为何常见:教科书上的公式通常假设“非退化情形”,而真实数据和简历中的凸多边形往往正好包含平行边。没有处理退化意识,是工程实现与理论推导的最大鸿沟。
误区我把所有半平面按角度排序后,用双端队列从 0 到 n-1 扫一遍,以为就完成了。结果最后发现核的边界没有闭合,或者漏掉了最后一个半平面与第一个半平面的交点。
正解半平面交是一个“环形”过程:最后一个半平面必须与第一个半平面再次求交,否则核的轮廓不闭合。正确做法是在扫描完所有半平面后,额外检查队尾交点是否在队首半平面内,以及队首交点是否在队尾半平面内,并把首尾交点补上。
为何常见:我们习惯把排序后的数组当线性结构处理,忘了角度排序后的半平面首尾其实是相邻的。像时针从 360° 回到 0°,首尾相接是绕圈的本质属性。
面试题
Q1. 半平面 \(H = \{(x,y) \mid ax+by+c \ge 0\}\) 表示的是直线的哪一侧?
- A.法向量 \((a,b)\) 指向的那一侧← 正确答案
- B.法向量 \((a,b)\) 背向的那一侧
- C.x 轴正方向的一侧
- D.y 轴正方向的一侧
答案:A。半平面的方向由法向量决定,\((a,b)\) 指向的那一侧满足 \(ax+by+c \ge 0\)。就像探照灯的光轴方向,灯头朝哪边,哪边就是亮的。选项 B 是相反侧,C 和 D 与笛卡尔坐标轴无关。
Q2. 极角排序时,半平面法向量的极角通常用什么函数计算?
- A.\(\arcsin\)
- B.\(\text{atan2}(b, a)\)← 正确答案
- C.\(\arccos\)
- D.\(\text{atan2}(a, b)\)
答案:B。\(\text{atan2}(b, a)\) 能根据 \((a,b)\) 的分量正确计算极角,值域是 \((-π, π]\)。而 \(\arcsin/\arccos\) 无法覆盖全部象限,\(\text{atan2}(a, b)\) 把参数顺序颠倒会导致角度方向错误。
Q3. 多边形的核非空时,这个多边形被称为什么?
- A.凸多边形
- B.星形多边形← 正确答案
- C.正多边形
- D.凹多边形
答案:B。核非空的多边形叫星形多边形,因为存在一个点能“看到”所有边。凸多边形一定是星形的,但星形不一定凸,所以 A 不准确;正多边形只是特殊凸多边形;凹多边形不一定有非空核。
Q4. 半平面交算法总的时间复杂度是多少?
- A.\(O(n)\)
- B.\(O(n \log n)\)← 正确答案
- C.\(O(n^2)\)
- D.\(O(n^3)\)
答案:B。极角排序是 \(O(n \log n)\),双端队列扫描阶段每个半平面最多入队、出队一次,是 \(O(n)\)。总复杂度取主导项就是 \(O(n \log n)\)。选项 A 忽略了排序成本,C 是两两求交的暴力算法,D 更不可能。
Q5. 在判断点 \(p\) 是否在半平面内时,最直接的方法是什么?
- A.计算点到直线的距离
- B.将 \(p\) 代入不等式 \(ax+by+c \ge 0\) 验证← 正确答案
- C.计算 \(p\) 到原点的距离
- D.比较 \(p\) 与直线端点的坐标大小
答案:B。半平面的定义就是不等式,所以直接把点的坐标代入即可。距离计算是多余的,原点距离与半平面方向无关,坐标比较只适用于轴对齐矩形,不适用于一般直线。
Q6. 用双端队列维护半平面交时,以下哪些位置的边可能被淘汰?
- A.队尾← 正确答案
- B.队首← 正确答案
- C.队列中间
- D.队列长度不变,无淘汰发生
答案:AB。新半平面加入时,队列末尾的交点可能不再满足新约束,所以可能从队尾弹出;扫描结束时,队首的交点可能被末尾的半平面淘汰,所以要检查队首。队列中间的元素在双端队列操作中不会被直接访问或删除。D 显然错误,淘汰是算法的核心步骤。
Q7. 当两条半平面边界直线平行且方向相反时,交集是什么?
- A.其中一个半平面
- B.空集← 正确答案
- C.一条直线
- D.一个点
答案:B。方向相反的平行线约束指向完全相反的两侧,如同两束光对着照却互不相让,公共区域必然是空的。方向相同且重叠时交集才可能是其中一个半平面,C 和 D 在非退化情况下不会出现。
Q8. 下列哪些情况会导致半平面交算法退化,需要特殊处理?
- A.两条直线平行← 正确答案
- B.两个半平面完全相同← 正确答案
- C.三个半平面交于同一点← 正确答案
- D.输入多边形是凸多边形
答案:ABC。平行线导致交点公式分母为零,需要判断保留或判空;完全相同的半平面排序后相邻,需去重;三线共点会导致核边界出现“顶点退化”,需要按边界包含处理。而凸多边形输入恰好是半平面交最标准的场景,不属于退化。
Q9. 在交点公式 \(t = \frac{\text{cross}(P_0 - Q_0, d_2)}{\text{cross}(d_2, d_1)}\) 中,分母 \(\text{cross}(d_2, d_1)\) 的几何意义是什么?
- A.两条直线的夹角大小
- B.两条直线方向向量的叉积,用于判断是否平行和计算参数← 正确答案
- C.两条直线的长度之积
- D.两条直线的法向量之和
答案:B。分母是方向向量的二维叉积,其绝对值正比于两向量围成平行四边形的面积。若叉积为零则直线平行。它同时出现在交点参数的分母中,是参数化求交的核心。A 是它的引申含义但不完整,C 和 D 与向量叉积无关。
Q10. 半平面交完成后,若核的边界由 \(k\) 个顶点组成,那么这些顶点的来源是什么?
- A.原始半平面边界两两相交的所有交点
- B.相邻半平面边界的交点,以及首尾半平面边界的交点← 正确答案
- C.原始多边形的全部顶点
- D.随机采样得到的点
答案:B。核的边界由排序后相邻的两个半平面边界相交而成,且最后一个半平面要与第一个半平面再求一次交,形成闭合。A 中的“所有交点”包含大量不在核边界上的点,是暴力法的思路;C 是原多边形顶点,核顶点通常不是它们;D 显然不可能。