凸包问题。给定平面上n个点,从中找出一个最小点集,使得该点集所组成的凸多边形包围所有的n个点。基于分治策略,设计一个求解凸包问题的算法。实现该算法并测试。
分治算法思路:
- 如果点集中的点数小于等于3,可以直接返回这些点作为凸包,因为凸包至少包括3个点。
- 将点集排序,用最左点 P 1 P_1 P1和最右点 P n P_n Pn连成的直线分成两个子集:上包和下包。
- 获得距离直线最远的点 P m a x P_{max} Pmax
- 作直线 P 1 P m a x 和 P n P m a x P_1 P_{max}和P_n P_{max} P1Pmax和PnPmax,分别对左侧和右侧求上包
- 递归地对上包进行凸包计算。
- 下包同理
两点构成的直线函数为:
y
−
y
1
y
2
−
y
1
=
x
−
x
1
x
2
−
x
1
\frac{y-y_1}{y_2-y_1} = \frac{x-x_1}{x_2-x_1}
y2−y1y−y1=x2−x1x−x1
代入点到直线的距离公式,得距离为:
∣
(
x
2
−
x
1
)
y
3
−
(
y
2
−
y
1
)
x
3
−
(
x
2
−
x
1
)
y
1
+
(
y
2
−
y
1
)
x
1
∣
(
x
2
−
x
1
)
2
+
(
y
2
−
y
1
)
2
\frac{|(x_2-x_1)y_3-(y_2-y_1)x_3-(x_2-x_1)y_1+(y_2-y_1)x_1|}{\sqrt{(x_2-x_1)^2+(y_2-y_1)^2}}
(x2−x1)2+(y2−y1)2∣(x2−x1)y3−(y2−y1)x3−(x2−x1)y1+(y2−y1)x1∣
因为我们需要求距离最远,因此只需要计算
∣
(
x
2
−
x
1
)
y
3
−
(
y
2
−
y
1
)
x
3
−
(
x
2
−
x
1
)
y
1
+
(
y
2
−
y
1
)
x
1
∣
=
∣
x
1
∗
y
2
+
x
3
∗
y
1
+
x
2
∗
y
3
−
x
3
∗
y
2
−
x
2
∗
y
1
−
x
1
∗
y
3
∣
|(x_2-x_1)y_3-(y_2-y_1)x_3-(x_2-x_1)y_1+(y_2-y_1)x_1| \\ =|x1 * y2 + x3 * y1 + x2 * y3 - x3 * y2 - x2 * y1 - x1 * y3|
∣(x2−x1)y3−(y2−y1)x3−(x2−x1)y1+(y2−y1)x1∣=∣x1∗y2+x3∗y1+x2∗y3−x3∗y2−x2∗y1−x1∗y3∣
最大即可。
代码如下:
import random
import matplotlib.pyplot as plt
from typing import List
# slot是为了减少内存开销, lt是为了排序, iter是为了方便取值
class Point:
__slots__ = ['x', 'y']
def __init__(self, x:float, y:float) -> None:
self.x = x
self.y = y
def __lt__(self, other):
if self.x == other.x:
return self.y < other.y
return self.x < other.x
def __iter__(self):
yield self.x
yield self.y
def cacl_dis(a:Point, b:Point, c:Point) -> float:
x1, y1 = a
x2, y2 = b
x3, y3 = c
return x1 * y2 + x3 * y1 + x2 * y3 - x3 * y2 - x2 * y1 - x1 * y3
def border_point_up(left_point:Point, right_point:Point, lists:List[Point], border_points:List[Point]) -> None:
dis_max = 0
max_point = None
for item in lists:
if item == left_point or item == right_point:
continue
else:
dis = cacl_dis(left_point, right_point, item)
if dis > dis_max:
max_point = item
dis_max = dis
if dis_max != 0:
border_points.append(max_point)
border_point_up(left_point, max_point, lists, border_points)
border_point_up(max_point, right_point, lists, border_points)
def border_point_down(left_point:Point, right_point:Point, lists:List[Point], border_points:List[Point]) -> None:
dis_max = 0
max_point = ()
for item in lists:
if item == left_point or item == right_point:
continue
else:
dis = cacl_dis(left_point, right_point, item)
if dis < dis_max:
max_point = item
dis_max = dis
if dis_max != 0:
border_points.append(max_point)
border_point_down(left_point, max_point, lists, border_points)
border_point_down(max_point, right_point, lists, border_points)
def order_border(lists: List[Point]) -> List[Point]:
lists.sort()
first_x, first_y = lists[0] # 最左边的点
last_x, last_y = lists[-1] # 最右边的点
list_border_up = [] # 上半边界
for item in lists:
x, y = item
if y > max(first_y, last_y):
list_border_up.append(item)
if min(first_y, last_y) < y < max(first_y, last_y):
if cacl_dis(lists[0], lists[-1], item) > 0:
list_border_up.append(item)
else:
continue
list_border_down = [_ for _ in lists if _ not in list_border_up] # 下半边界
list_end = list_border_up + list_border_down[::-1] # 最终顺时针输出的边界点
return list_end
def draw(list_points:List[Point], list_borders:List[Point]) -> None:
list_all_x = []
list_all_y = []
for item in list_points:
a, b = item
list_all_x.append(a)
list_all_y.append(b)
list_borders.append(list_borders[0])
plt.scatter(list_all_x, list_all_y)
for i in range(len(list_borders) - 1):
one_, oneI = list_borders[i]
two_, twoI = list_borders[i + 1]
plt.plot([one_, two_], [oneI, twoI], color='red')
plt.scatter([one_, two_], [oneI, twoI], color='red')
plt.show()
# 生成随机点集
def generate_random_points(n:int, xmin:float, xmax:float, ymin:float, ymax:float)->List[Point]:
return [Point(random.uniform(xmin, xmax), random.uniform(ymin, ymax)) for _ in range(n)]
if __name__ == "__main__":
n = 100
x_min, x_max = 0, 100
y_min, y_max = 0, 100
list_points = generate_random_points(n, x_min, x_max, y_min, y_max)
list_points.sort()
border_points = [] # 边界点集
border_point_up(list_points[0], list_points[-1], list_points, border_points) # 上边界点集
border_point_down(list_points[0], list_points[-1], list_points, border_points) # 下边界点集
border_points.append(list_points[0])
border_points.append(list_points[-1])
draw(list_points, order_border(border_points))
这里随机初始化100个点来测试代码,并且将结果通过matplotlib展示出来: