本篇文章将介绍二维的Graham扫描算法和它的源码实现。
Graham扫描算法会先将点按照极角的大小顺序排列,接着按顺序遍历每个点,通过夹角的大小判断哪些点在凸包上,算法的伪码如下所示:
求给定二维点集的凸包
- 求出最左下角的点,即
分量的值最小点,若分量值最小的点有多个,取分量最小的点,设为;
- 剩下的点集,根据极角大小,逆时针排序,得到
;
- 令栈
为空,用表示栈中第个元素,用表示栈中的元素个数;若栈中有个元素,则即为栈顶元素;每一次PUSH}操作,栈元素个数加1;每次POP操作,栈元素个数减1;
- PUSH(
,);
- PUSH(
,);
- for(
;;)
- while (
&& 由,和的夹角)
- POP(
);
- end while;
- end for;
- if
, then
- 点集无法生成一个凸包;
- else
- 栈
中的个元素,就是凸包的顶点;
- end if;
以如图1(a)所示为例,给定一个点集,
。第1步,选择最左下角的点
,若
分量值最小的点有多个,取
分量最小的点,特别注意,在实际实现中,可能存在多个相同的
点,必需把它们排除,保证点
是唯一的,否则在第2步中的排序会造成错误。第2步,如图(b)所示,对
按照极角从小到大进行排序,若极角相同,则根据与点
的距离从近到远排序。第3 ~ 5步,如图(c)所示,把
和
两个点压入栈中。逆时针方向处理每个顶点,第6 ~ 10步,如图(d)~(i),若
且由
,
和
的夹角大于等式180度,则把栈顶元素出栈,否则把新点
压入栈。最终得到的凸包如图(k)所示,点
在线段
上,是否把它作为凸包的顶点,可以根据具体的问题再作决定,上面提供的算法伪码,不允许把点
作为凸包的顶点。
对于对极点的排序,有多种算法实现,比如冒泡排序,快速排序,堆排序等,各种排序的性能对比,如表1的总结。若采用冒泡排序,则Graham扫描算法的复杂度将达到
,采用其它几种排序算法,Graham算法的复杂度都能达到
。采用快速排序,平均复杂度是
,在C++中,提供了qsort()快速排序的算法,但在最坏情况下,Graham算法的复杂度仍然是
。因此,可以选择归并排序或者堆排序。
基础库源码链接,参见这里,下面是前面所描述的算法的实现。
#include