📅  最后修改于: 2023-12-03 14:50:10.200000             🧑  作者: Mango
凸包是一个凸多边形,是这个多边形凸包含所有点且所有点都在凸包内部。类比于一个面包,外层是面包皮,里面是馅料,面包皮就相当于凸包。
计算凸包算法非常多,最常见的一般有:Graham算法,Jarvis算法,Andrew算法,QuickHull算法,Divide And Conquer算法等等。
这里介绍其中一种非常常见、简单易懂的算法:单调链算法。
单调链算法是一种时间复杂度为 $O(n \log n)$ 的求凸包算法,在实际中比较常用。单调链算法的思想是:先按照 $x$ 坐标排序,然后把点分成两部分:上凸壳和下凸壳。对于两个凸壳的求解过程是类似的,只是在某些过程中,比较方式不同而已。
对所有点按照x轴排序,找出最左边和最右边的点,分别作为左右边框上的点,分别命名为 Oleft, ORight
。
扫描全部点,将他们全部放置在两条链表上,第一个点放在第一条链表上,然后顺序处理每个点,如果是逆时针旋转,插入左侧链表,否则插入右侧链表。
合并上下凸壳,左右两个不相交的单调链合并为一个单调链。在这个过程中,为了确定下凸壳需要退栈操作。
输出凸包上的所有点。
按照上述思路,这里给出单调链算法的Python实现。
import functools
# 判断两个向量的角度大小,小于180度视为逆时针,大于180度视为顺时针
def cross(u, v):
return u[0]*v[1] - u[1]*v[0]
def cmp(a, b):
# 比较两个点的位置关系,用于排序
if a[0] != b[0]:
return a[0] - b[0]
return a[1] - b[1]
# 计算凸包的主函数
def convex_hull(points):
# 对点按照x坐标排序,并找到左右两个边界点
left = functools.reduce(lambda x, y: x if x[0] < y[0] else y, points)
right = functools.reduce(lambda x, y: x if x[0] > y[0] else y, points)
# 根据点集排序规则进行排序
sorted_points = sorted(points, key=functools.cmp_to_key(cmp))
# 初始化上、下凸壳
top, bottom = [left], [left]
# 处理剩余点
for p in sorted_points[1:]:
# 如果是逆时针旋转,插入到上凸壳中
if cross((p[0]-top[-1][0], p[1]-top[-1][1]), (top[-1][0]-top[-2][0], top[-1][1]-top[-2][1])) > 0:
while len(top) > 1 and cross((p[0]-top[-1][0], p[1]-top[-1][1]), (top[-1][0]-top[-2][0], top[-1][1]-top[-2][1])) > 0:
top.pop()
top.append(p)
# 如果是顺时针旋转,插入到下凸壳中
if cross((p[0]-bottom[-1][0], p[1]-bottom[-1][1]), (bottom[-1][0]-bottom[-2][0], bottom[-1][1]-bottom[-2][1])) < 0:
while len(bottom) > 1 and cross((p[0]-bottom[-1][0], p[1]-bottom[-1][1]), (bottom[-1][0]-bottom[-2][0], bottom[-1][1]-bottom[-2][1])) < 0:
bottom.pop()
bottom.append(p)
# 合并上下凸壳
hull = top + bottom[1:-1][::-1]
# 返回凸包上的点
return hull