📅  最后修改于: 2023-12-03 14:50:10.263000             🧑  作者: Mango
凸包是一种重要的计算几何问题,它指的是覆盖所有给定点的凸多边形。Quickhull 算法是一种高效的求解凸包问题的算法,它的时间复杂度为 $O(n \log n)$。
Quickhull 算法基于分治的思想,将凸包分成左右两个部分,递归地处理这两个部分。对于每个部分,首先选取当前集合中最左和最右的两个点作为凸壳边界上的点,然后找到其他点中离这条边界线最远的点,将其加入凸壳边界中。递归处理左右两个部分,最终得到凸包。
下面介绍 Quickhull 算法的具体实现。
Quickhull 算法的函数接口如下:
def quick_hull(points):
"""
Quickhull 算法求凸包
Args:
points: 点集,格式为 [(x1, y1), (x2, y2), ...]
Returns:
凸包点组成的列表,格式为 [(x1, y1), (x2, y2), ...]
"""
函数输入是点集,格式为一个包含若干个坐标元组的列表。函数的返回值是凸包点组成的列表,格式为一个包含若干个坐标元组的列表。
在实现 Quickhull 算法之前,需要先实现一些辅助函数:
def dist(p1, p2, p):
"""
计算点 p 到 p1, p2 所在直线的距离
Args:
p1: 直线上的点,格式为 (x1, y1)
p2: 直线上的点,格式为 (x2, y2)
p: 需要计算距离的点,格式为 (x, y)
Returns:
点 p 到 p1, p2 所在直线的距离
"""
def find_furthest_point(points, p1, p2):
"""
在点集 points 中找到距离 p1, p2 所在直线最远的点,返回该点和距离
Args:
points: 点集,格式为 [(x1, y1), (x2, y2), ...]
p1: 直线上的点,格式为 (x1, y1)
p2: 直线上的点,格式为 (x2, y2)
Returns:
最远点和距离,格式为 ((x, y), distance)
"""
def divide(points, p1, p2):
"""
将点集 points 分成 p1, p2 两侧和 p1, p2 之间三个部分,返回这三个部分构成的点集
Args:
points: 点集,格式为 [(x1, y1), (x2, y2), ...]
p1: 直线上的点,格式为 (x1, y1)
p2: 直线上的点,格式为 (x2, y2)
Returns:
三个部分分别构成的点集,格式为 (left, on, right)
"""
这些辅助函数会在 Quickhull 算法的实现中使用到。
根据 Quickhull 算法的思想,可以递归地求解凸包问题:
def quick_hull(points):
if len(points) <= 3:
return points
# 找到最左和最右的点
left = min(points, key=lambda p: p[0])
right = max(points, key=lambda p: p[0])
# 将点集分成左右两部分和中间一部分
left_points, on_points, right_points = divide(points, left, right)
# 递归求解左右两部分的凸包
left_hull = quick_hull(left_points)
right_hull = quick_hull(right_points)
# 合并左右两部分的凸包
hull = left_hull + right_hull
# 将中间一部分添加到凸包中
if on_points:
hull.append(on_points[0])
# 递归处理左右两部分之间的凸包边界
for p1, p2 in [(left, right), (right, left)]:
# 找到 p1, p2 两侧距离最远的点
p, _ = find_furthest_point(points, p1, p2)
# 将该点添加到凸包边界中
if p not in hull:
hull.append(p)
return hull
这里采用了递归的方式求解凸包问题,算法的复杂度为 $O(n \log n)$。
完整代码实现如下:
def dist(p1, p2, p):
return abs((p[1] - p1[1]) * (p2[0] - p1[0]) - (p2[1] - p1[1]) * (p[0] - p1[0])) / \
((p2[1] - p1[1]) ** 2 + (p2[0] - p1[0]) ** 2) ** 0.5
def find_furthest_point(points, p1, p2):
max_dist = -1
max_point = None
for p in points:
if p in (p1, p2):
continue
d = dist(p1, p2, p)
if d > max_dist:
max_dist = d
max_point = p
return max_point, max_dist
def divide(points, p1, p2):
left, on, right = [], [], []
for p in points:
if p in (p1, p2):
on.append(p)
elif (p[0] - p1[0]) * (p2[1] - p1[1]) > (p[1] - p1[1]) * (p2[0] - p1[0]):
right.append(p)
else:
left.append(p)
return left, on, right
def quick_hull(points):
if len(points) <= 3:
return points
left = min(points, key=lambda p: p[0])
right = max(points, key=lambda p: p[0])
left_points, on_points, right_points = divide(points, left, right)
left_hull = quick_hull(left_points)
right_hull = quick_hull(right_points)
hull = left_hull + right_hull
if on_points:
hull.append(on_points[0])
for p1, p2 in [(left, right), (right, left)]:
p, _ = find_furthest_point(points, p1, p2)
if p not in hull:
hull.append(p)
return hull