分治法应用-凸包问题
问题描述
给定一个平面上n个点的集合,它的凸包就是包含所有这些点的最小凸多边形,求取满足此条件的所有点。
主要分为以下三个方面设计(思考):
1)初始化:随机生成某范围内一定规模的随机点集
2)问题求解:使用分治法思想求解初始化点集的凸包
3)可视化:分步显示出求解过程
功能模块详细设计
1.初始化
为方便演示设置了X和Y坐标的上限upper和下限lower,通过列表推导式和random.randint()函数生成设置范围内指定数量pointSize个坐标,再通过循环遍历把分散的X,Y坐标合并成点坐标加入点集points中。在利用plt.figure创建好的区域上,利用已生成的坐标再画出散点图。
pointsSize = 100 # 100个点
upper = 100 # 点的坐标上限为100
lower = -100 # 点的坐标下限为-100
x_ = [random.randint(lower, upper) for i in range(pointsSize)]
y_ = [random.randint(lower, upper) for i in range(pointsSize)]
points = []
f = plt.figure(figsize=(7,5), dpi=100)
for i in range(pointsSize):
points.append([x_[i], y_[i]])
for i in range(0, pointsSize):
plt.scatter(x_[i], y_[i], facecolor='purple', s=20,alpha=0.5)
2.分治凸包算法
###2.1 判断三点间的位置关系–caculate函数
由 P1(x1, y1), P2 (x2, y2), P3 (x3, y3)三点的坐标组成3*3的行列式,表示三点围成的三角形面积,可以得出 在 和 确定的直线的上侧还是下侧。若值为正则在上侧,为负则在下侧,等于0则三点共线。定义caculate函数,参数为三点坐标,返回值为面积计算结果。
2.2 上包函数—findUpperMax
采用快包法,检测处于上凸包集合pointsup里的点,遍历集合全部的点,找到此时与底边距离最远的点,并连线。如果算出是在底边下侧的点,则设置标志让其坐标设为值-1,在下次递归时筛选出来,无需计算此点,即实现了将不和条件的点从上包点集中舍去的目的。如果遍历结果没有使上凸包的面积取到最大的点,则证明底边已经是凸包边界,需要结束,并输出。如果找到了使上凸包面积取到面积最大的点,则用该点与底边端点连线分成左凸包和右凸包,即分成两个规模更小的子问题,再重复递归问题。
细节:为了使左右凸包结果不相互影响,使用copy.deepcopy深复制来复制点集,分成pointsleft和pointsright。为了可以存储凸包点,用全局变量onside_point保存。
def findUpperMax(x1, y1, x2, y2, points):
# 选取上包部分的一个点,该点为上包部分最远点
# x1, y1: 边界上的左端点
# x2, y2: 边界上的右端点
# points: 可选点集
# f: figure
flag = False
upperMax = 0
upperx = uppery = 0
for i in range(0,len(points)):#筛选点的过程
if points[i]!=-1:
if points[i][0] == x1 and points[i][1] == y1 or points[i][0] == x2 and points[i][1] == y2:
continue
S = caculate(x1,y1,x2,y2,points[i][0],points[i][1])
if S > 0:
if S > upperMax:
flag = True
upperMax = S
upperx = points[i][0]
uppery = points[i][1]
else:
points[i]=-1
if flag == False:
#flag作为标志,如果为F则,在图上连线
plt.plot([x1, x2], [y1, y2], color='r')
#把凸包边界上的点无重复的加入凸包点集onside_point
if [x1,y1] not in onside_point:
onside_point.append([x1,y1])
if [x2,y2] not in onside_point:
onside_point.append([x2,y2])
return
plt.plot([x1, upperx], [y1, uppery], color='b')
plt.plot([x2, upperx], [y2, uppery], color='b')
#可分为两段(左端点,顶点)递归,(顶点,右端点)递归
pointsleft=copy.deepcopy(points)
pointsright=copy.deepcopy(points)
findUpperMax(x1, y1, upperx, uppery, pointsleft)
findUpperMax(upperx, uppery, x2, y2, pointsright)
2.3 下包函数—findBottomMin
与上包的求解类似,只需修改找到使与底边围成面积最小的点,即为下包上的点。类似的做出下包递归的左右问题子集,递归求解。如果没找见,则连线变成下凸包的边。
3.可视化
3.1 在tkinter窗体显示
matplotlib与tkinter集成主要是通过两个类完成的,总体来说是画图三步走。
把绘制的图形显示到tkinter窗口上
canvas=FigureCanvasTkAgg(figure,self.root)
canvas.draw()
canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=1)
所以只需要把在matplotlib作出的图传入即可。
把matplotlib绘制图形的导航工具栏显示到tkinter窗口上,总体也是分三步实现。
toolbar=NavigationToolbar2Tk(self.canvas,self.root)
toolbar.update()
self.canvas._tkcanvas.pack(side=tk.TOP, fill=tk.BOTH, expand=1)
设置窗体的大小和位置
root = TK.Tk()
root.title("分治凸包")
root.geometry(f'{650}x{555}+{400}+{120}')
root.mainloop()
3.2 按钮
(1)下一步的实现
通过在每次需要画图显示时,分为两种情况。
情况1.在计算凸包的过程中,如果计算找到凸包上的点时,需要连线该点和底边的两点做出辅助线,便于理解分治法的分解子问题,在三角形内的部分就不在需要访问。继续分为左边的顶点集和右边的顶点集,递归求解。而且为了便于区分上下凸包的求解过程,使用不同颜色的辅助线,上包用蓝色表示,下包用绿色表示。
情况2.在递归过程中,如果未找见凸包顶点,则证明底边为凸包边界,此时应该输出。
只需在程序对应的位置输出图像即可。所谓下一步就是可以关闭当前的图像,并在处理好之后在重新输出的过程。因为这样,所以算是以程序本身驱动的。
def _quit():#下一步按钮调用的函数
root.destroy()
#设置按钮‘下一步’
button1 = TK.Button(master=root, text=’下一步',width=5,height=1,command=_quit)
button1.place(x=250,y