问题
用Graham Scan算法求拥有n个点的点集Q的凸包CH(Q),任意两点的坐标不同。
解法
在二维平面上选取y坐标最小,x坐标最小的顶点p0,将其余n−1个顶点[p1,p2,…,pn−1]按照以p0的顺时针方向排序。如图所示:
上图中以p0点为基准,对其余点排序的结果为[p1,p2,p3,p4,p5,p6,p7,p8]。
根据Cross中向量叉积的知识,可知对于三个顶点p0,p1,p2组成的向量p0p1,p0p2,设C=p0p1×p0p2有以下情况:
(1) 若C>0则p0p2在p0p1逆时针方向。如图:
(2) 若C<0则p0p2在p0p1顺时针方向。如图:
(3) 若C=0则p0p1与p0p2方向相同。对于向量完全相同的两顶点p1,p2,按照到p0距离从近到远排序。如图:
排序时比较任意两点到p0的向量叉积都为负数,则整组顶点可以按照顺时针排列。设排列后的顶点顺序为[p1,p2,…,pn−1]。
设置一个空堆栈stack=∅,初始时推入三个顶点p0,p1,p2,此时堆栈为stack=[p0,p1,p2]。
然后遍历剩余顶点[p3,…,pn−1],对于每个顶点pi,考虑堆栈stack的头部顶点ptop、次头部顶点pnext−top,判断这三个点组成的两个向量ptoppi和pnext−topptop,前者是否在后者的顺时针方向,即满足
pnext−topptop×ptoppi<0 (1) 若pi不满足条件,说明不是凸包上的点,对堆栈stack进行出栈操作(推出头部顶点top)。然后再重复判断pnext−topptop×ptoppi的值,直到满足该条件为止;
(2) 若pi满足条件,说明是凸包上的点,将其推入堆栈stack中。然后继续考虑下一个顶点pi+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
遍历完点集Q中所有顶点后,堆栈stack中的点即为Q的凸包CH(Q)。该算法的时间复杂度为O(n⋅log2n)。
Introduction to Algorithms
源码
GrahamScan.h
GrahamScan.cpp
测试
GrahamScanTest.cpp