此分治法解决凸包问题演示程序是基于PyQt5库与matplotlib库进行开发的窗体动态演示程序。采用python面向对象知识对窗体进行类封装,通过matplotlib.backends.backend_qt5agg类连接PyQt5在GUI中呈现图像。通过用户点击按钮进行交互,能够一步步的演示用分治法生成凸包时从无到有的整个过程。功能包含查看分治法凸包问题原理、生成凸包、退出程序三个部分,运行程序时通过一直点击“下一步“按钮,可以动态地查看用分治法生成凸包的整个过程。
第一步:把所有点在横坐标方向上按从小到大顺序排列,左右两个端点必定是凸包上的点
第二步:在上凸包集合点中找一个距离直线最远的点,将它与左右端点连接起来
第三步:重复递归求解到上半凸包的边
第四步:下半凸包同理,通过递归求解,最终得到整个凸包
个人认为讲的最清楚一看就懂的一个博客超详细的凸包问题的分治算法说明及C++实现
演示效果:
演示代码.py
import sys
import random
import matplotlib
matplotlib.use('Qt5Agg')
from PyQt5 import QtCore, QtWidgets
from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import QApplication, QMainWindow, QMenu, QVBoxLayout, QHBoxLayout, QSizePolicy, QWidget, \
QTextBrowser, QLineEdit, QPushButton, QMessageBox
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
from matplotlib.animation import FuncAnimation
import matplotlib.pyplot as plt
xx = [random.randint(1, 10) for i in range(10)]
yy = [random.randint(1, 10) for i in range(10)]
class MyMplCanvas(FigureCanvas):
def __init__(self, parent=None, width=12, height=6, dpi=100):
fig = Figure(figsize=(9, 7), dpi=100)
self.ax = fig.add_subplot(1, 1, 1)
FigureCanvas.__init__(self, fig)
self.setParent(parent)
FigureCanvas.setSizePolicy(self,
QtWidgets.QSizePolicy.Expanding,
QtWidgets.QSizePolicy.Expanding)
FigureCanvas.updateGeometry(self)
class ApplicationWindow(QtWidgets.QMainWindow):
def __init__(self):
QtWidgets.QMainWindow.__init__(self)
self.index = 0
self.dic = {}
self.pointsSize = 30
self.upper = 100
self.lower = -100
self.xx = [random.randint(self.lower, self.upper) for i in range(self.pointsSize)]
self.yy = [random.randint(self.lower, self.upper) for i in range(self.pointsSize)]
self.points = [[self.xx[i], self.yy[i]] for i in range(self.pointsSize)]
self.points.sort(key=lambda x: (x[0], x[1]))
self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
self.setWindowTitle("分治法生成凸包演示by18070542孙泽坤")
self.main_widget = QtWidgets.QWidget(self)
self.xs = []
self.ys = []
self.pointers = 0
self.game_start()
vbox = QtWidgets.QVBoxLayout(self.main_widget)
self.canvas = MyMplCanvas(self.main_widget, width=6, height=6, dpi=100) ###attention###
vbox.addWidget(self.canvas)
hbox = QtWidgets.QHBoxLayout(self.main_widget)
self.textBrowser = QTextBrowser(self)
self.button = QPushButton("下一步")
self.quitButton = QPushButton("退出")
self.introduceButton = QPushButton("查看凸包问题原理介绍")
self.introduceButton.clicked.connect(self.introduce)
self.button.clicked.connect(self.choice_point)
self.quitButton.clicked.connect(self.exit_pragram)
vbox.addWidget(self.button)
vbox.addWidget(self.quitButton)
vbox.addWidget(self.introduceButton)
vbox.addWidget(self.textBrowser)
self.setLayout(vbox)
self.main_widget.setFocus()
self.setCentralWidget(self.main_widget)
self.ani = FuncAnimation(self.canvas.figure, self.update_line, interval=15)
self.textBrowser.append("先把最左边和最右边的两个边界点({},{})和({},{})连接起来"
.format(self.points[0][0]
, self.points[0][1]
, self.points[self.pointsSize - 1][0]
, self.points[self.pointsSize - 1][1]))
def caculate(self, x1, y1, x2, y2, x3, y3):
return x1 * y2 + x3 * y1 + x2 * y3 - x1 * y3 - x2 * y1 - x3 * y2
def judge(self, x1, x2, y1, y2):
pta = tuple([x1, y1])
ptb = tuple([x2, y2])
numa = self.dic.get(pta, [])
numb = self.dic.get(ptb, [])
if ptb in numa or pta in numb:
return False
numa.append(ptb)
numb.append(pta)
self.dic[pta] = numa
self.dic[ptb] = numb
return True
def findUpperMax(self, x1, y1, x2, y2):
flag = False
upperMax = 0
upperx = uppery = 0
for i in self.points:
if i[0] == x1 and i[1] == y1 or i[0] == x2 and i[1] == y2:
continue
areaData = self.caculate(x1, y1, x2, y2, i[0], i[1])
if areaData > 0:
if areaData > upperMax:
flag = True
upperMax = areaData
upperx = i[0]
uppery = i[1]
if flag == False:
if self.judge(x1, x2, y1, y2) == False:
return
self.xs.append([x1, x2])
self.xs.append([y1, y2])
return
if self.judge(x1, upperx, y1, uppery) == False:
return
self.xs.append([x1, upperx])
self.xs.append([y1, uppery])
if self.judge(x2, upperx, y2, uppery) == False:
return
self.xs.append([x2, upperx])
self.xs.append([y2, uppery])
self.findUpperMax(x1, y1, upperx, uppery)
self.findUpperMax(upperx, uppery, x2, y2)
def findBottomMin(self, x1, y1, x2, y2):
flag = False
lowerMax = 0
lowerx = lowery = 0
for i in self.points:
if i[0] == x1 and i[1] == y1 or i[0] == x2 and i[1] == y2:
continue
areaData = self.caculate(x1, y1, x2, y2, i[0], i[1])
if areaData < 0:
if areaData < lowerMax:
flag = True
lowerMax = areaData
lowerx = i[0]
lowery = i[1]
if flag == False:
if self.judge(x1, x2, y1, y2) == False:
return
self.xs.append([x1, x2])
self.xs.append([y1, y2])
return
if self.judge(x1, lowerx, y1, lowery) == False:
return
self.xs.append([x1, lowerx])
self.xs.append([y1, lowery])
if self.judge(x2, lowerx, y2, lowery) == False:
return
self.xs.append([x2, lowerx])
self.xs.append([y2, lowery])
self.findBottomMin(x1, y1, lowerx, lowery)
self.findBottomMin(lowerx, lowery, x2, y2)
def scatter_points(self):
self.canvas.ax.plot([self.points[0][0], self.points[self.pointsSize - 1][0]],
[self.points[0][1], self.points[self.pointsSize - 1][1]], color='black')
for i in range(0, self.pointsSize):
self.canvas.ax.scatter(self.xx[i], self.yy[i], color='k', s=20)
def game_start(self):
self.findUpperMax(self.points[0][0], self.points[0][1], self.points[self.pointsSize - 1][0],
self.points[self.pointsSize - 1][1])
self.findBottomMin(self.points[0][0], self.points[0][1], self.points[self.pointsSize - 1][0],
self.points[self.pointsSize - 1][1])
def choice_point(self):
self.textBrowser.clear()
if self.pointers == len(self.xs):
self.textBrowser.append("凸包生成完毕!点击退出键退出")
return
self.ys.append(self.xs[self.pointers])
self.ys.append(self.xs[self.pointers + 1])
# print(self.xs[self.pointers][0], self.xs[self.pointers + 1][0],
# self.xs[self.pointers][1], self.xs[self.pointers + 1][1])
text = "选择点({0},{1})和点({2},{3}),把它们连接起来" \
.format(self.xs[self.pointers][0], self.xs[self.pointers + 1][0],
self.xs[self.pointers][1], self.xs[self.pointers + 1][1])
self.textBrowser.append(text)
self.pointers += 2
def update_line(self, i):
self.canvas.ax.clear()
self.scatter_points()
for i in range(0, len(self.ys), 2):
a = self.ys[i]
b = self.ys[i + 1]
if i == len(self.ys) - 2:
self.canvas.ax.plot(a, b, color='red')
else:
self.canvas.ax.plot(a, b, color='black')
if i == len(self.ys) - 2:
self.canvas.ax.scatter(self.ys[i][0], self.ys[i + 1][0], color='red')
self.canvas.ax.scatter(self.ys[i][1], self.ys[i + 1][1], color='red')
def introduce(self):
text = "第一步:把所有点在横坐标方向上按从小到大顺序排列,左右两个端点必定是凸包上的点\n第二步:在上凸包集合点中找一个距离直线最远的点,将它与左右端点连接起来\n第三步:重复递归求解到上半凸包的边\n第四步:下半凸包同理,通过递归求解,最终得到整个凸包"
reply = QMessageBox.information(self, '分治法凸包问题原理介绍', text, QMessageBox.Yes)
def exit_pragram(self):
sys.exit(0)
if __name__ == "__main__":
App = QApplication(sys.argv)
aw = ApplicationWindow()
aw.show()
App.exit()
sys.exit(App.exec_())