并行与分布式系统中的并行凸包算法:基于Graham扫描的并行化方法
问题描述
给定二维平面上的一个点集 P,包含 n 个点。凸包(Convex Hull)是指包含点集 P 中所有点的最小凸多边形。经典的Graham扫描算法可以在 O(n log n) 的时间内(通过排序)串行地求解凸包。在并行与分布式环境中,我们希望利用多处理器或分布式节点,将点集分割到不同处理单元上,并行地计算局部凸包,然后高效地合并这些局部结果,从而加速整个凸包的构建过程。本题目将详细讲解基于Graham扫描思想的并行凸包算法,包括点集划分、局部凸包计算、全局合并等关键步骤。
背景与挑战
- Graham扫描算法的核心是:首先找到最左下角的点(作为基准点),然后按相对于该点的极角排序所有点,最后使用栈维护凸包点。
- 在并行化时,直接排序和扫描的串行依赖性强,难以并行。因此,并行化常采用“分治-合并”策略。
- 主要挑战在于:如何划分点集使得负载均衡?如何高效合并多个凸包(每个凸包可能包含许多点)?如何避免合并时的串行瓶颈?
算法步骤详解
步骤1:点集划分
假设我们有 p 个处理器(或节点),点集 P 的大小为 n。
- 将点集 P 均匀划分为 p 个子集 P₁, P₂, …, P_p,每个子集大小约为 n/p。
- 划分策略通常采用随机划分(简单、负载均衡),或基于空间划分(如将平面划分为网格,每个网格对应一个子集,但可能负载不均)。
- 每个处理器 i 获得子集 P_i,负责计算 P_i 的凸包 CH_i。
步骤2:并行计算局部凸包
每个处理器 i 独立计算其子集 P_i 的凸包 CH_i。可以使用经典的Graham扫描算法(或其他凸包算法,如Andrew's monotone chain)。
- Graham扫描的串行步骤:
a. 在 P_i 中找到最左下角的点 p0(y坐标最小,若相同则x坐标最小)。
b. 将 P_i 中其余点按相对于 p0 的极角排序(使用叉积判断,避免反三角函数)。
c. 维护一个栈 S,初始将 p0 和排序后的第一个点入栈。
d. 按顺序处理每个点:若栈顶两个点与该点构成非左转(即叉积 ≤0),则弹出栈顶;否则将该点入栈。
e. 栈中最终点序列即为逆时针顺序的凸包 CH_i。 - 由于每个子集独立计算,这一步完全并行,无通信开销。
步骤3:全局合并
这是并行算法的核心。所有局部凸包 CH_i 需要合并成一个全局凸包 CH。合并的关键是:找到全局凸包的“上凸包”和“下凸包”(或称为上链和下链)。
- 观察:全局凸包由所有局部凸包的“外围”点组成,内部点会被丢弃。
- 合并策略(并行化合并):
a. 选取两个极值点:找到所有点中 x 坐标最小的点 L 和 x 坐标最大的点 R(可以通过一次并行归约实现)。L 和 R 一定在全局凸包上,并将凸包分为上链(Upper Hull)和下链(Lower Hull)。
b. 对于每个局部凸包 CH_i,将其分为两部分:位于直线 L-R 上方的点(可能属于上链)和下方的点(可能属于下链)。这一步可以并行进行。
c. 并行合并上链:
i. 对于每个局部凸包 CH_i,提取其位于 L-R 上方的点集 U_i(按 x 坐标排序)。
ii. 将所有 U_i 合并成一个点集 U(大小可能为 O(n),但实际中远小于 n)。
iii. 在 U 上运行一次Graham扫描(或Andrew's monotone chain的上链构建)得到上链。这一步是串行的,但输入规模已大幅减小。
d. 并行合并下链:类似地,合并所有下链点集 D_i 得到下链。
e. 合并上链和下链(去除 L 和 R 的重复),得到全局凸包 CH。
步骤4:负载均衡与优化
- 在步骤3c和3d中,合并所有 U_i 和 D_i 可能成为瓶颈。可进一步并行化:
a. 使用多路归并(如并行归并树)合并排序后的 U_i。
b. 或者,不显式合并所有点,而是使用“分治合并凸包”的并行递归:将局部凸包两两合并(类似于归并排序),形成一棵合并树。每次合并两个凸包可以使用旋转卡壳(Rotating Calipers)技术在线性时间内完成。
c. 合并树的高度为 O(log p),每层可并行合并,总时间复杂度降低。 - 实际实现中,常采用“分治合并”策略,算法框架如下:
- 每个处理器计算局部凸包 CH_i。
- 重复直到只剩一个凸包:
- 将凸包配对,每对凸包合并成一个新凸包(使用旋转卡壳法求两个凸包的凸包)。
- 合并操作在可用处理器上并行执行。
- 最终凸包即为全局凸包。
步骤5:合并两个凸包的旋转卡壳法
这是分治合并的关键子程序,输入两个凸包 CH_A 和 CH_B(均按逆时针排列),输出它们的凸包 CH_{A∪B}。
- 步骤:
a. 在 CH_A 和 CH_B 上分别找到 x 坐标最小的点(最左点)和 x 坐标最大的点(最右点)。这四个点帮助确定上、下公切线。
b. 求上公切线:从最右点开始,同时在两个凸包上逆时针移动,直到同时与两个凸包都相切(即连线两侧没有点)。
c. 求下公切线:类似,从最左点开始顺时针移动。
d. 合并:从下切点开始,沿 CH_A 走到上切点,再沿 CH_B 走到下切点,构成新凸包。 - 旋转卡壳法时间复杂度为 O(|CH_A|+|CH_B|),且易于并行化(例如,在多核上同时计算上下切线)。
复杂度分析
- 步骤2:每个处理器计算大小为 n/p 的点集的凸包,时间复杂度 O((n/p) log(n/p))。
- 步骤3(分治合并):合并树高度 O(log p),每层合并总点数为 O(n)(因为所有局部凸包点数和为 O(n)),但旋转卡壳法线性时间,故总合并时间 O(n log p / p)(若分配 p 个处理器并行合并)。
- 总体时间:O((n/p) log(n/p) + (n log p)/p)。当 p ≪ n 时,主要开销在局部计算,加速比接近线性。
- 通信开销(分布式环境):划分点集需分发数据;合并时需要收集局部凸包点或中间结果。可采用树形通信模式,总通信量为 O(n log p)。
实例说明
假设 n=16 个点,p=4 个处理器。
- 随机划分点集为4组,每组4个点。
- 每个处理器独立计算其4个点的凸包(可能为三角形或四边形)。
- 处理器1和2合并其凸包,处理器3和4合并其凸包(并行执行)。
- 再将两个合并后的凸包合并,得到最终凸包。
合并时使用旋转卡壳法快速找到公切线,丢弃内部点。
总结
基于Graham扫描的并行凸包算法通过“局部计算+分治合并”实现了高效并行化。关键点在于利用旋转卡壳法在线性时间内合并凸包,并通过合并树结构将合并过程并行化。该算法适用于共享内存多处理器或分布式内存集群,能够有效处理大规模点集的凸包计算问题。注意实际实现中需考虑负载均衡(如随机划分)和通信优化(如减少合并时的数据传输)。