python实现凸包算法——包裹法
一些结果
代码
"""
在矩形区域内随机N个点,求解它们的凸包。要求采取的算法时间复杂度较低。给出算法思路,运用任意语言实现快速凸包算法,并就执行时间与N的关系进行量化分析。
本作业提交代码和调研分析文档。文档按规定的字体和行间距等进行排版(同上),文档提交PDF版本即可,通常在2页左右,文档中不排版代码。
"""
import random
import math
import matplotlib.pyplot as plt
import time
# 使能够正常显示中文
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
"""定义一些参数 把点随机生成出来"""
# 生成随机数,并设置随机数种子,以便复现
random.seed(10)
points_number = 10000 # 设置凸包点数
point_x_axis_list = random.sample(range(-points_number * 10, points_number * 10), points_number)
point_y_axis_list = random.sample(range(-points_number * 10, points_number * 10), points_number)
print(f"point_x_axis_list:{point_x_axis_list}")
print(f"point_y_axis_list:{point_y_axis_list}")
"""end"""
class DrawPoint(object):
"""画点的类"""
def __init__(self):
self.fontsize = 14
self.scatter_s = 5
self.connect_point_color = 'r'
self.plot_marker = 'H'
self.plt_obj = plt
def draw_points(self, point_x_axis_list: list, point_y_axis_list: list, xlabel: str, ylabel: str, title: str):
"""把点显示出来"""
self.plt_obj.scatter(point_x_axis_list, point_y_axis_list, s=self.scatter_s)
# # 设置图表标题并给坐标轴加上标签
self.plt_obj.title(title, fontsize=self.fontsize)
self.plt_obj.xlabel(xlabel, fontsize=self.fontsize)
self.plt_obj.ylabel(ylabel, fontsize=self.fontsize)
def connect_points(self, point_x_axis_list: list, point_y_axis_list: list):
"""按顺序连接各点"""
self.plt_obj.plot(point_x_axis_list, point_y_axis_list, color=self.connect_point_color, marker=self.plot_marker)
def plt_show(self):
self.plt_obj.show()
class PointsProcess(object):
"""点的处理类"""
def __init__(self):
pass
def calculate_two_points_distance(self, start_point_x, start_point_y, end_point_x, end_point_y):
"""两点之间的欧氏距离"""
two_points_distance = math.sqrt((start_point_x - end_point_x) ** 2 + (start_point_y - end_point_y) ** 2)
two_points_distance = round(two_points_distance, 3)
# print(f"点({start_point_x},{start_point_y})和点({end_point_x},{end_point_y})的距离为:{two_points_distance}")
return two_points_distance
def calculate_three_points_angle(self, point1_x, point1_y, point2_x, point2_y, point3_x, point3_y):
"""计算3点之间的夹角,默认返回中间点的夹角,使用余弦定理"""
distance1_2 = self.calculate_two_points_distance(point1_x, point1_y, point2_x, point2_y)
distance2_3 = self.calculate_two_points_distance(point2_x, point2_y, point3_x, point3_y)
# print(f"distance1_2:{distance1_2},distance2_3:{distance2_3}")
distance1_3 = self.calculate_two_points_distance(point1_x, point1_y, point3_x, point3_y)
_temp_value = (distance1_3 * distance1_3 - distance1_2 * distance1_2 - distance2_3 * distance2_3) / (
-2 * distance1_2 * distance2_3)
# 防止超过数学领域
if _temp_value > 1:
_temp_value = 1
elif _temp_value < -1:
_temp_value = -1
angle2 = math.degrees(math.acos(_temp_value))
angle2 = round(angle2, 3)
# print(f"点({point1_x},{point1_y})、({point2_x},{point2_y})、({point3_x},{point3_y})对应的angle2为:{angle2}")
return angle2
def calculate_three_points_angle2(self, distance1_2, point1_x, point1_y, point2_x, point2_y, point3_x, point3_y):
"""计算3点之间的夹角,默认返回中间点的夹角,使用余弦定理"""
distance1_2 = distance1_2
distance2_3 = self.calculate_two_points_distance(point2_x, point2_y, point3_x, point3_y)
# print(f"distance1_2:{distance1_2},distance2_3:{distance2_3}")
distance1_3 = self.calculate_two_points_distance(point1_x, point1_y, point3_x, point3_y)
angle2 = int(math.degrees(math.acos(
(distance1_3 * distance1_3 - distance1_2 * distance1_2 - distance2_3 * distance2_3) / (
-2 * distance1_2 * distance2_3))))
# print(f"点({point1_x},{point1_y})、({point2_x},{point2_y})、({point3_x},{point3_y})对应的angle2为:{angle2}")
return angle2
def cal_angle_two_points_and_xaxis(self, start_point_x, start_point_y, end_point_x, end_point_y):
"""计算两点之间与x轴的夹角"""
dx = end_point_x - start_point_x
dy = end_point_y - start_point_y
angle = None
# 两个点在同一列上
if dx == 0:
# 箭头向上,90度
if dy > 0:
angle = 90
# 箭头向下,270度
elif dy < 0:
angle = 270
# 两个点重叠一般这种情况不存在,没有意义,这里设置为180度
else:
angle = 180
# 不在同一列上
else:
if dx > 0:
if dy > 0: # 箭头指向右上角,ok
angle = int(math.atan(dy / dx) / math.pi * 180)
elif dy < 0: # 箭头指向右下角 算出的角度是负数,要加2pi
angle = int((math.atan(dy / dx) + math.pi * 2) / math.pi * 180)
else: # dy = 0 说明在同一行,箭头指向坐标轴右侧
angle = 0
else:
if dy == 0: # 箭头指向坐标轴左侧
angle = 180
else: # dx < 0 箭头指向左上角或者左下角,计算方法都一样。在左上角时算出是负数要+pi,在右下角时算出是正数要+pi
angle = int((math.atan(dy / dx) + math.pi) / math.pi * 180)
# print(f"点({start_point_x},{start_point_y})和点({end_point_x},{end_point_y})的角度angle为:{angle}度")
return angle
draw_obj = DrawPoint()
draw_obj.draw_points(point_x_axis_list=point_x_axis_list, point_y_axis_list=point_y_axis_list, xlabel="x", ylabel="y",
title="凸包点显示")
draw_obj.plt_show()
"""*************************************************方法一 包裹法*********************************************"""
pack_way_start_time = time.time()
acme_index_list = [] # 凸包顶点
points_index = [] # 点索引列表
for i in range(len(point_x_axis_list)):
points_index.append(i)
point_process_obj_for_pack_way = PointsProcess()
draw_obj_for_pack_way = DrawPoint()
draw_obj_for_pack_way.draw_points(point_x_axis_list=point_x_axis_list, point_y_axis_list=point_y_axis_list, xlabel="x",
ylabel="y",
title=f"凸包包裹法实现,共{points_number}个点")
# 选择凸包的最低点作为起点,同时也是第一个顶点。但是点索引集不能去掉起点,不然找不到首尾对应的break条件
lowest_point_index = point_y_axis_list.index(min(point_y_axis_list))
acme_index_list.append(lowest_point_index)
# print(f"lowest_point_index:{lowest_point_index}")
start_point_index = lowest_point_index
point1_index = start_point_index
point2_index = None
point3_index = None
while True:
if len(acme_index_list) == 1:
"""找第二个顶点,和起点的角度要最小"""
# 最小的角度初始化为360度,最小角度索引还不知道,为空
min_angle = 360
min_angle_index = None
for current_point_index in points_index:
angle = point_process_obj_for_pack_way.cal_angle_two_points_and_xaxis(
start_point_x=point_x_axis_list[start_point_index],
start_point_y=point_y_axis_list[start_point_index],
end_point_x=point_x_axis_list[current_point_index],
end_point_y=point_y_axis_list[current_point_index])
if angle < min_angle:
min_angle = angle
min_angle_index = current_point_index
# print(f"min_angle:{min_angle},min_angle_index:{min_angle_index}")
acme_index_list.append(min_angle_index) # 找到最小角度的点为新的凸包顶点,加入凸包顶点列表中
start_point_index = min_angle_index # 新的凸包顶点作为新的起点,同时点索引集没有它了
points_index.remove(min_angle_index)
point2_index = min_angle_index
else:
"""找其它顶点,前2个点和后2个点的角度要最大"""
max_angle = 0
max_angle_index = None
for current_point_index in points_index:
angle = point_process_obj_for_pack_way.calculate_three_points_angle(
point1_x=point_x_axis_list[point1_index],
point1_y=point_y_axis_list[point1_index],
point2_x=point_x_axis_list[point2_index],
point2_y=point_y_axis_list[point2_index],
point3_x=point_x_axis_list[current_point_index],
point3_y=point_y_axis_list[current_point_index])
if angle > max_angle:
max_angle = angle
max_angle_index = current_point_index
acme_index_list.append(max_angle_index) # 找到最小角度的点为新的凸包顶点,加入凸包顶点列表中
points_index.remove(max_angle_index)
# 更新索引
point1_index = point2_index
point2_index = max_angle_index
# 直到回到原点时退出
if lowest_point_index == max_angle_index:
break
# print(f"acme_index_list:{acme_index_list}")
# 生成凸包顶点列表
print(f"acme_index_list:{acme_index_list}")
acme_x_list = [point_x_axis_list[i] for i in acme_index_list]
acme_y_list = [point_y_axis_list[i] for i in acme_index_list]
print(f"acme_x_list:{acme_x_list}")
print(f"acme_y_list:{acme_y_list}")
# 画图
draw_obj_for_pack_way.connect_points(point_x_axis_list=acme_x_list, point_y_axis_list=acme_y_list)
draw_obj_for_pack_way.plt_show()
pack_way_end_time = time.time()
print(f"包裹法花费的时间为:{round((pack_way_end_time - pack_way_start_time), 5)}s")
"""*************************************************end*********************************************************"""
本文来自博客园,作者:JaxonYe,转载请注明原文链接:https://www.cnblogs.com/yechangxin/articles/17000492.html
侵权必究