python3实现复杂度nlogn算法求平面上最短点对(带Tkinter界面)

因为对这个分治算法不是特别熟,而且第一次用Tkinter做界面,花了两天时间才做好,其中遇到了各种问题,幸好坚持下去并求助百度上的各路大神和文档写完了。

感谢博客:https://blog.csdn.net/Lytning/article/details/25370169博主

补充(2019.2.12)代码我进行了后续的界面优化和功能分离,将带GUI和不同数据量测试运行时间写在两个py文件里,代码和实验报告已经上传到GitHub git链接,或者http链接,欢迎批评指点!

因为我写的分治算法代码基本上是由博客的相关内容将C++转化成python3,所以文章应该属于转载。

实现功能:使用Tkinter做了GUI,生成指定数量随机位置的点,支持用户点击输入点,用分治算法和常规算法分别计算输出最短距离和最短点对

话不多说,先粘代码!!!(之后有时间详细来篇解析,不过真心感觉我参考博主的思路已经很清晰了!!!)

# coding=utf-8
"""
Created on 2019.2.10
@author: 刘祥
分治求最短点对算法参考博客:https://blog.csdn.net/Lytning/article/details/25370169
"""
import numpy as np
from tkinter import *
import random
import math

from operator import itemgetter #用来对字典列表根据特定属性进行排序

MAX_DISTANCE = float('inf')
class ClosestPairs(object):

    def __init__(self, size=1000, total_w=10000, total_h=10000, canvas_w=1200, canvas_h=700):
        self.size = size

        self.window = Tk()
        self.window.title("Find Closest Pairs")

        #设定frame和canvas的显示宽高
        self.frame_w = self.canvas_w = canvas_w
        self.frame_h = self.canvas_h = canvas_h

        #创建frame
        self.frame = Frame(self.window,width=self.frame_w,height=self.frame_h)
        self.frame.grid(row=0,column=0)

        #canvas的全部宽高
        self.total_w = total_w
        self.total_h = total_h

        #创建canvas
        self.canvas = Canvas(self.frame, 
                        width=self.canvas_w, 
                        height=self.canvas_h, 
                        background="white",
                        scrollregion=(0,0,self.total_w, self.total_h)
                    )

        #canvas绑定点击事件
        self.canvas.bind("<Button -1>", self.add_point)

        #圆点半径
        self.dot_radius = 2
        #可选坐标集合
        self.x_list = list(range(self.dot_radius, self.total_w - self.dot_radius))
        self.y_list = list(range(self.dot_radius, self.total_h - self.dot_radius))
        #实际坐标集合
        # 使用random模块中的random.sample函数产生一个数值范围内的不重复的随机数
        self.posx_list = random.sample(self.x_list, size)
        self.posy_list = random.sample(self.y_list, size)

        #声明点列表
        self.points_list = []
        # self.temp_points_list = []  #存储points_list按照x和y属性排序的结果

        #水平滚动条
        self.hbar=Scrollbar(self.frame,orient=HORIZONTAL) 
        self.hbar.pack(side=BOTTOM,fill=X)
        self.hbar.config(command=self.xview) 
        self.hbar_offset = 0
  
        #竖直滚动条 
        self.vbar=Scrollbar(self.frame,orient=VERTICAL)   
        self.vbar.pack(side=RIGHT,fill=Y)
        self.vbar.config(command=self.yview)
        self.vbar_offset = 0

        #canvas属性设置
        self.canvas.config(xscrollcommand=self.hbar.set, yscrollcommand=self.vbar.set) #设置  
        self.canvas.pack(expand=True,fill=BOTH)

        #存储最近的两个点
        self.point_a = {}
        self.point_b = {}

        #为了寻找最近点对,存储当前的距离
        self.curr_distance = MAX_DISTANCE

    def xview(self, MOVETO, f):
        self.canvas.xview(MOVETO,f)
        # print(MOVETO)
        self.hbar_offset = round(float(self.total_w*eval(f)))
        # print("移动百分比",eval(f))
        # print("移动的像素是%f\n"%float(self.total_w*eval(f)))

    def yview(self, MOVETO, f):
        self.canvas.yview(MOVETO,f)
        # print(MOVETO)
        self.vbar_offset = round(float(self.total_w*eval(f)))
        # print("移动百分比",eval(f))
        # print("移动的像素是%f\n"%float(self.total_w*eval(f)))

    def generate_random_points(self):    
        for i in range(self.size):
            point = {}
            point["x"] =  self.posx_list[i]
            point["y"] =  self.posy_list[i]
            self.points_list.append(point)
            self.canvas.create_oval(self.posx_list[i]-self.dot_radius, self.posy_list[i]-self.dot_radius, self.posx_list[i]+self.dot_radius, self.posy_list[i]+self.dot_radius, fill='red')
        self.window.mainloop()

    def sort_x(self, points_list):
        return sorted(points_list, key=itemgetter('x'))
        

    def sort_y(self, points_list):
        return sorted(points_list, key=itemgetter('y'))

    
    def get_distance(self, point_a, point_b):
        return math.sqrt((point_a["x"]-point_b["x"])**2 + (point_a["y"]-point_b["y"])**2)

    def get_closest_pairs(self, left, right):
        if left == right:
            return MAX_DISTANCE
        if left+1 == right:
            dis = self.get_distance(self.points_list[left], self.points_list[right])
            #加入寻找最近点对的相关代码
            if dis < self.curr_distance:
                self.curr_distance = dis
                self.point_a = self.points_list[left]
                self.point_b = self.points_list[right]
            return dis

        mid = (left + right) // 2    #取x的中点
        d1 = self.get_closest_pairs(left, mid)
        d2 = self.get_closest_pairs(mid, right)
        d = min(d1, d2)

        temp_list = []
        for i in range(left, right+1):
            if math.fabs(self.points_list[i]["x"] - self.points_list[mid]["x"]) <= d:
                temp_list.append(self.points_list[i])

        temp_list = self.sort_y(temp_list)
        temp_len = len(temp_list)
        for i in range(temp_len):
            for j in range(i+1, temp_len):
                #不超过六个点进入此循环
                if temp_list[j]['y'] - temp_list[i]['y'] < d:
                    d3 = self.get_distance(temp_list[i], temp_list[j])
                    d = min(d, d3)
                    #加入寻找最近点对的相关代码
                    if d == d3 and d < self.curr_distance:
                        self.curr_distance = d
                        self.point_a = temp_list[i]
                        self.point_b = temp_list[j]
                else:
                    break
        return d
    
    def add_point(self, event):
        new_point = {}
        new_point["x"] = self.hbar_offset + event.x
        new_point["y"] = self.vbar_offset + event.y
        print("点击的坐标是(%d,%d)"%(new_point["x"],new_point['y']))
        #判断点是否重复
        if new_point in self.points_list:
            print("您点击的点已经记录在界面上存在!")
            return
        self.size += 1
        self.canvas.create_oval(new_point["x"]-self.dot_radius,new_point["y"]-self.dot_radius,new_point["x"]+self.dot_radius, new_point["y"]+self.dot_radius, fill='blue')
        self.points_list.append(new_point)
        self.points_list = self.sort_x(self.points_list)
        print("分治算法得到点击加入点后的最短点对距离为:%f  最短点对的坐标是(%d,%d),(%d,%d)"
            %(self.get_closest_pairs(0, self.size-1), self.point_a['x'], self.point_a['y'], self.point_b['x'], self.point_b['y']))
        
    def brute_closest_pairs(self):
        init_distance = MAX_DISTANCE
        for i in range(self.size):
            for j in range(i+1, self.size):
                dis = self.get_distance(self.points_list[i], self.points_list[j])
                if dis < init_distance:
                    init_distance = dis
                    self.point_a = self.points_list[i]
                    self.point_b = self.points_list[j]
        print("常规算法得到最终平面上最短点对距离是:%f  最短点对的坐标是(%d,%d),(%d,%d)"
            %(init_distance, self.point_a['x'], self.point_a['y'], self.point_b['x'], self.point_b['y']))
        

if __name__ == '__main__':
    size = int(input("请输入点的数量:"))
    cp = ClosestPairs(size,2000,2000)
    cp.generate_random_points()
    cp.points_list = cp.sort_x(cp.points_list)
    print("分治算法得到最终平面上最短点对距离是:%f  最短点对的坐标是(%d,%d),(%d,%d)"
            %(cp.get_closest_pairs(0, cp.size-1), cp.point_a['x'], cp.point_a['y'], cp.point_b['x'], cp.point_b['y']))
    cp.brute_closest_pairs()

收获:

1.分治算法求最短点对

2.了解tkinter做GUI的一些基础知识点,如Frame中嵌套canvas,

设定canvas可滚动区域,canvas绑定点击事件获取鼠标点击坐标并加入点

range(n)转list,

使用random模块中的random.sample函数产生一个数值范围内的不重复的随机数,

自己写滚动函数,就是针对滚动条滚动操作的处理函数

用填充颜色的小圆代表圆点(说实话之前没找到canvas绘制point函数我快要疯了!!!)

对元素为字典的数组根据字典的某一属性进行排序!!!(不得不说python真的很强,这些函数都不需要我们自己写)

我是用字典表示点的如{"x":1,"y":2},其实用class和元组都可以表示点point,并且根据某一属性排序!!!

相关内容请参考博客《python sort、sorted高级排序技巧》,真的很棒!!!

最后就是感觉python的class很好用,之前一直都是函数式编程,写一大堆的函数互相调用,

但是前几天看到一个博客说如果这些函数有共同的数据的话就可以封装成一个类,个人感觉挺有道理的。

3.最后不得不说学习编程有时候看官方文档比网上搜索可能效率会更高一些!

因为在重写滚动条的滚动事件时,我不知道传入参数,所以查了很多网页都不是完整的,但是一查文档就很清楚了。

欢迎大家评论指点!

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值