GrahamScan Graham扫描算法

问题

用Graham Scan算法求拥有nn个点的点集QQ的凸包CH(Q)CH(Q),任意两点的坐标不同。

解法

在二维平面上选取yy坐标最小,xx坐标最小的顶点p0p_0,将其余n1n-1个顶点[p1,p2,,pn1][p_1, p_2, \dots, p_{n-1}]按照以p0p_0的顺时针方向排序。如图所示:

上图中以p0p_0点为基准,对其余点排序的结果为[p1,p2,p3,p4,p5,p6,p7,p8][p_1, p_2, p_3, p_4, p_5, p_6, p_7, p_8]

根据Cross中向量叉积的知识,可知对于三个顶点p0,p1,p2p_0, p_1, p_2组成的向量p0p1,p0p2\vec{p_0 p_1}, \vec{p_0 p_2},设C=p0p1×p0p2C = \vec{p_0 p_1} \times \vec{p_0 p_2}有以下情况:

(1) 若C>0C \gt 0p0p2\vec{p_0 p_2}p0p1\vec{p_0 p_1}逆时针方向。如图:

(2) 若C<0C \lt 0p0p2\vec{p_0 p_2}p0p1\vec{p_0 p_1}顺时针方向。如图:

(3) 若C=0C = 0p0p1\vec{p_0 p_1}p0p2\vec{p_0 p_2}方向相同。对于向量完全相同的两顶点p1,p2p_1, p_2,按照到p0p_0距离从近到远排序。如图:

排序时比较任意两点到p0p_0的向量叉积都为负数,则整组顶点可以按照顺时针排列。设排列后的顶点顺序为[p1,p2,,pn1][p_1, p_2, \dots, p_{n-1}]

设置一个空堆栈stack=stack = \varnothing,初始时推入三个顶点p0,p1,p2p_0, p_1, p_2,此时堆栈为stack=[p0,p1,p2]stack = [p_0, p_1, p_2]

然后遍历剩余顶点[p3,,pn1][p_3, \dots, p_{n-1} ],对于每个顶点pip_i,考虑堆栈stackstack的头部顶点ptopp_{top}、次头部顶点pnexttopp_{next-top},判断这三个点组成的两个向量ptoppi\vec{p_{top} p_i}pnexttopptop\vec{p_{next-top} p_{top}},前者是否在后者的顺时针方向,即满足

pnexttopptop×ptoppi<0\vec{p_{next-top} p_{top}} \times \vec{p_{top} p_i} \lt 0

(1) 若pip_i不满足条件,说明不是凸包上的点,对堆栈stackstack进行出栈操作(推出头部顶点toptop)。然后再重复判断pnexttopptop×ptoppi\vec{p_{next-top} p_{top}} \times \vec{p_{top} p_i}的值,直到满足该条件为止;

(2) 若pip_i满足条件,说明是凸包上的点,将其推入堆栈stackstack中。然后继续考虑下一个顶点pi+1p_{i+1}

用伪代码来描述上述过程是

for i = 3 to n-1:
  while stack.size() >= 2 and ccw(stack.nextTop(), stack.top(), p[i]) <= 0:
    stack.pop()
  stack.push(p[i])
end

遍历完点集QQ中所有顶点后,堆栈stackstack中的点即为QQ的凸包CH(Q)CH(Q)。该算法的时间复杂度为O(nlog2n)O(n \cdot log_2 n)

Introduction to Algorithms

源码

GrahamScan.h

GrahamScan.cpp

测试

GrahamScanTest.cpp

Last updated