第一步:把给定点集中的点在横坐标方向上按照大小排序。找到横坐标相距最大的两个点p1和p2,这两个点必定是凸包上的两个点,如下图所示。
第二步:在点集合中找到与p1和p2构成三角形面积大于0的所有点,在这些点中找到构成面积的点p_max,如下图所示。显然直线段p1 p_max与直线段p_max p2把点集分成了三个集合。由凸包的性质可知p1 p_max p2三点围成的三角形中的点不可能作为凸包的顶点,所以只需考虑直线p1 p_max左边的点以及直线p_max p2右边的点。
第三步:递归求解得到凸多边形的边。
第四步:合并这些边即得所求凸包。
from threading import Thread
import matplotlib.pyplot as plt
from matplotlib.widgets import Button
import random
# 生成所有的点
all = []
def all_points():
n = int(input("生成的随机点数量:"))
while len(all) < n:
x = random.randint(0, 250)
y = random.randint(0, 250)
if [x, y] not in all:
all.append([x, y])
return all
# 计算面积
def area(a, b, c):
x1, y1 = a
x2, y2 = b
x3, y3 = c
return x1 * y2 + x3 * y1 + x2 * y3 - x3 * y2 - x2 * y1 - x1 * y3
# 将所有的点绘制在坐标图上
def draw(list_points):
list_all_x = []
list_all_y = []
for item in list_points:
a, b = item
list_all_x.append(a)
list_all_y.append(b)
ax.scatter(list_all_x, list_all_y)
# 自定义类,用来封装三个按钮的单击事件处理函数
class ButtonHandler:
def __init__(self,left_point, right_point, lists, border_points):
self.area_max = 0
self.point_max = ()
self.left_point=left_point
self.right_point=right_point
self.lists = lists
self.border_points=border_points
# 线程函数,用来更新数据并重新绘制图形
# 寻找上半部分的边界点,并连成线段
def Thread_up(self):
ax.plot([self.left_point[0], self.right_point[0]], [self.left_point[1], self.right_point[1]], color='y')
plt.pause(1)
for item in self.lists:
if item == self.left_point or item == self.right_point:
continue
else:
new_area = area(self.left_point, self.right_point, item)
if new_area > self.area_max:
self.point_max = item
self.area_max = new_area
if self.area_max != 0:
self.border_points.append(self.point_max)
a = ButtonHandler(self.left_point, self.point_max, self.lists, self.border_points)
a.Thread_up()
b = ButtonHandler(self.point_max, self.right_point, self.lists, self.border_points)
b.Thread_up()
def up1(self):
for item in self.lists:
if item == self.left_point or item == self.right_point:
continue
else:
new_area = area(self.left_point, self.right_point, item)
if new_area > self.area_max:
self.point_max = item
self.area_max = new_area
if self.area_max != 0:
self.border_points.append(self.point_max)
a = ButtonHandler(self.left_point, self.point_max, self.lists, self.border_points)
a.up1()
b = ButtonHandler(self.point_max, self.right_point, self.lists, self.border_points)
b.up1()
# 寻找下半部分的边界点,并连成线段
def Thread_down(self):
ax.plot([self.left_point[0], self.right_point[0]], [self.left_point[1], self.right_point[1]], color='y')
plt.pause(1)
for item in self.lists:
if item == self.left_point or item == self.right_point:
continue
else:
new_area = area(self.left_point, self.right_point, item)
if new_area < self.area_max:
self.point_max = item
self.area_max = new_area
if self.area_max != 0:
border_points.append(self.point_max)
c = ButtonHandler(self.left_point, self.point_max, self.lists, border_points)
c.Thread_down()
d = ButtonHandler(self.point_max, self.right_point, self.lists, border_points)
d.Thread_down()
def down1(self):
for item in self.lists:
if item == self.left_point or item == self.right_point:
continue
else:
new_area = area(self.left_point, self.right_point, item)
if new_area < self.area_max:
self.point_max = item
self.area_max = new_area
if self.area_max != 0:
border_points.append(self.point_max)
c = ButtonHandler(self.left_point, self.point_max, self.lists, border_points)
c.down1()
d = ButtonHandler(self.point_max, self.right_point, self.lists, border_points)
d.down1()
def Up(self, event):
t = Thread(target=self.Thread_up)
t.start()
def Down(self, event):
t = Thread(target=self.Thread_down)
t.start()
def draw(self,event):
self.border_points.sort()
first_x, first_y = self.border_points[0] # 最左边的点
last_x, last_y = self.border_points[-1] # 最右边的点
list_border_up = [] # 上半边界
for item in self.border_points:
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 area(self.border_points[0], self.border_points[-1], item) > 0:
list_border_up.append(item)
else:
continue
list_border_down = [_ for _ in self.border_points if _ not in list_border_up] # 下半边界
list_end = list_border_up + list_border_down[::-1] # 最终顺时针输出的边界点
list_end.append(list_end[0])
for i in range(len(list_end) - 1):
one_, oneI = list_end[i]
two_, twoI = list_end[i + 1]
ax.plot([one_, two_], [oneI, twoI],color='r')
if __name__ == "__main__":
fig, ax = plt.subplots()
plt.subplots_adjust(bottom=0.2)
all_points=all_points() # 生成所有的点
all_points.sort()
left_p, right_p = all_points[0], all_points[-1] # 所有点中横坐标相距最大的两个点
border_points = [] # 边界点集
draw(all_points)
# 创建按钮并设置单击事件处理函数
callback = ButtonHandler(left_p, right_p, all_points, border_points)
axprev = plt.axes([0.81, 0.05, 0.1, 0.075])
bprev = Button(axprev, 'UP')
bprev.on_clicked(callback.Up)
down = ButtonHandler(left_p, right_p, all_points, border_points)
axnext = plt.axes([0.7, 0.05, 0.1, 0.075])
bnext = Button(axnext, 'DOWN')
bnext.on_clicked(down.Down)
e=ButtonHandler(left_p, right_p, all_points, border_points)
e.up1()
e.down1()
border_points.append(left_p)
border_points.append(right_p) # 将首尾两个点添加到边界点集中
print(border_points) # 输出边界点
zuihou = plt.axes([0.59, 0.05, 0.1, 0.075])
tubao = Button(zuihou, 'tubao')
tubao.on_clicked(e.draw)
plt.show()