“本单元的核验脚本故意用直接扫描活动边寻找左邻,使 helper 状态容易逐行审计;这个参考实现是 $O(n^2)$,不是平衡树实现的速度证据。它核验插入对角线可见、分块两链单调、面积和保持,…”
形式陈述
如果一个简单多边形已经单调,为什么还要像耳切一样反复搜索整条边界?设输入为逆时针
维护一个栈,初始压入
- 若
与栈顶在不同链,从栈顶向下输出以 为第三顶点的三角形,直到栈只剩一个点;随后把旧栈顶与 作为新的两点栈 - 若在同一链,先弹出旧栈顶
。令当前栈顶为 ,只要对角线 位于当前未剖区域内,就输出三角形 ,再令 并继续弹栈。停止后把最后的 和 压回
同链可见性可以由一个叉积判断。两链从最高点走向最低点,若
右链则为大于零。这里的顺序是“新点、刚弹出的点、下一栈顶”,换顺序必须同时换符号。最后用最低点
直觉
扫过的顶点不全都需要留着。已经围成三角形的部分可以立即封存;尚未剖分的上方区域,只留下一个由一条边和一段凹链围成的口袋。栈保存这段凹链,从底到顶高度下降。
新点来到另一条链时,能从另一侧看见整段栈链,所以可以一口气剖掉这个口袋。新点仍在同一链时,只有栈顶附近的一段变得可见;顺着凸转向弹栈,遇到第一个遮挡方向便停。条件不是“边长比较”或“顶点高度更低”,而是当前未处理区域的几何形状。
这给出归纳不变量:已输出三角形内部不交;未处理区域仍由未扫描边界和栈上的凹链组成;每条新对角线位于这个区域中。异链清空和同链逐个弹出都保持它,所以无需每次重新对全部边做线段相交检测。
例子与边界
取单调分块页中第三个面,循环边界为
仍使用其坐标及高度
初始栈为
它位于该面的右链,所以这个符号允许弹出,输出
接着处理
这个历史同时包含异链清空、同链成功弹栈和同链停止三种动作。若把右链判断误写为小于零,第二步就会留下本应剖掉的口袋;若忽略链标签,只凭同一个正负号处理两侧,镜像输入会立即失败。
图中的“栈”与单调栈题里的值序列不同。这里的相邻项保存的是边界的几何凹链,并没有要求
推论与应用
每个输入顶点只在首次扫描时加入;用于连接下一轮的旧栈顶可能被保留一次。每次非恒定工作都输出三角形或永久弹掉一个栈记录,总压栈、弹栈次数都是
本单元的独立核验程序为简化事件表使用通用排序;其所展示的栈阶段仍逐次记录压栈、弹栈,但整份参考程序的时间包含排序成本。算法的线性实现应直接归并输入边界两链,而不是用参考代码的排序步骤冒充线性。
平局先用一致的符号剪切消除,方向谓词仍取原坐标。若有共线边界点,不能输出零面积三角形来凑数;可规范化连续共线点并保留映射,或采用支持边界细分的完整退化版本。非单调多边形必须先划分,否则“另一链能看见整个栈”的关键性质没有依据。
与耳切相比,这里的速度来自输入已经提供两条单调链这一额外结构。它既不优化最小角,也不保证 Delaunay 空圆性。用于路径查询或有限元组装时,剖分合法性和三角形质量仍是两份不同检查。
参考资料
- Mark de Berg 等,Computational Geometry: Algorithms and Applications,第 3 版,2008,§3.3,TriangulateMonotonePolygon 及凹链不变量,印刷 pp.55–59。课程原书
- Dave Mount,Polygon Triangulation,2026,pp.3–6,单调剖分的同链/异链执行与线性计数。