题意

我们先来看下题意吧,题意很简单,在一个平面当中分布着n个点。现在我们知道这n个点的坐标,要求找出这n个点当中距离最近的两个点的间距。

分治法

如果我们仔细思考一下,会发现这个问题和排序其实非常类似。因为我们在排序的时候,表面上来看每两个点之间都存在大小关系,我们要排序似乎也要获得这些关系。但实际上,我们都知道,无论是快速排序还是归并排序都可以做到的时间内完成排序。
无论是快速排序还是归并排序,本质上都是利用的分治法。那么这道题是否也可以使用分治法求解呢?
答案当然是可以的,既然是使用分治法,那么我们首先要做的就是拆分,将整个的数据拆成两个部分,使用递归分别完成两个部分,然后再合并得到完整的结果。在这个问题当中,我们要拆分数据非常简单,只需要按照x轴坐标对所有点进行排序,然后选择中点进行分割即可,分割之后我们得到的结果如下:

拆分结束之后,我们只需要分别统计左边部分的最近点对、右边部分的最近点对,以及一个点在左边一个点在右边的最近点对即可。对于前面两种情况都很好解决,我们只需要递归就可以搞定了,但对于第三种情况应该怎么办?这也是本题的难点所在。

求出了D之后,我们就可以用它来限定一个点在SL一个点在SR这种情况的点对的范围了,不然的话我们要比较两边各有n/2个点的情况,依然计算复杂度很大。

我们来分析一下问题,我们在左侧随便选择一个点p,我们来想一个问题,对于点p而言,SR一侧所有的点都有可能与它构成最近点对吗?当然不是,有一些离得远的是明显不可能的,对于这些点我们没有必要一一遍历,直接都可以批量忽略。要想和p点构成最近点对,必须在下图这个虚线框起来的范围内。

这个虚线构成的框是一个长方形,它的宽是D,长是2D。这是怎么来的呢?其实很简单,对于p点来说,要想和他构成全局的最近点对,那么距离它的距离一定要小于目前的最优解D。既然距离要小于D,那么意味着它们的横纵坐标之差的绝对值必须也要小于D。
当然这个框只是我们直观看到的,在实际算法运行的时候是没有这个框的,我们只能根据坐标轴自己去进行判断某个点在不在框里。
有了这个框之后,我们产生了另外一个问题,那就是这个框到底可以起到多大的作用呢?有了这个框就可以降低算法复杂度了吗?会不会出现右侧所有点都在框里的极端情况呢?
其实我们只需要简单分析一下就会发现这是不可能的,不仅可以判断出这是不可能的,而且我们还可以得出另外一个非常非常惊人的结论。
首先,我们来论证一下为什么右侧所有的点都落在这个虚线框里是不可能的。我们先来看最极端的情况,最极端的情况就是我们选中的p点就在分割线上。那么以它画出来的框应该全部都落在SR区域,画成图大概是这样的:

但是我们简单想一下会发现一个问题,就是这个虚线框里的点的数量不可能是无限的。因为对于框里的点我们有一个基本的要求,就是在这个框里并且在SR区域内的点,两两之间的距离不得小于D。如果小于D了就和我们刚才得到D是左右两侧最小距离的结论矛盾了。那么上面图中的情况其实是不可能的,因为这么多点聚集在一起明显存在两个点的距离小于D了,这就矛盾了。

也就是说由于存在这个距离的限制,能够落在这个虚线框里的点的数量是有限的,而且这个数量比大家想的也许要小得多,有多小呢?小到最多只有6个,也就是下面这种情况:

在上图当中,一共有6个点,这6个点两两之间的最短距离是D,这是最极端的情况。无论我们如何往其中加入点,都一定会产生两个点之间的距离小于D。这是我们很直观的感受,有没有办法证明呢?其实也是有的,我们可以把这个矩形进行六等分变成下图这样:

我们来分析一下,上图的每一个小矩形的长是,宽是,它的对角线长度是。那么根据鸽笼原理,如果我们放入超过6个点,必然会存在一个小矩形内存在两个点。而小矩形内最大的距离小于D,也就是说这两个点的距离必然也小于D,这就和我们之前的假设矛盾了,所以可以得出超过7个点的情况是不存在的。
也就是说对于SL侧的点p,我们在SR侧最多只能找出6个点来可能构成最短点对,这样我们需要筛查的点对数量就大大减小。并且对于SL侧的点来说,并不是所有的点都需要考虑的,只有和中点O横坐标差值小于D的点才需要考虑。
表面上看起来我们所有的分析都结束了,但实际上还有一个问题没有解决。就是我们怎么样找到这6个点呢?显然只根据横坐标是不行的,这个时候就需要考虑纵坐标了。我们将点集分成左右两个部分之后,对右侧部分按照纵坐标进行排序,对于左侧的点(x, y)而言,我们只需要筛选出右侧满足纵坐标范围在(y - d, y + d)范围内的点,这样的点最多只有6个。我们可以利用二分法找到纵坐标大于 y - d的最小的点,然后依次枚举之后的6个点即可。

代码实现

在我们实现算法之前,我们需要先生成测试数据,否则如何验证我们的算法是否有问题呢?而且这个算法也是我从头开发的,对于debug也有帮助。
在这道题当中,测试数据还是比较简单的,只需要生成两个随机数作为坐标即可。我们调用这个方法先生成200个点作为测试。

import randomdef random_point():x, y = random.uniform(0, 1000), random.uniform(0, 1000)return (x, y)points = [random_point() for _ in range(200)]

接着我们再实现暴力解法,用来检测我们的算法的正确性,这一段我想应该不用我多说,大家都能理解。

def distance(x, y):return math.sqrt((x[0] - y[0]) ** 2 + (x[1] - y[1])** 2)def brute_force(points):ret = sys.maxsizea, b = None, Nonen = len(points)for i in range(n):for j in range(i+1, n):dis = distance(points[i], points[j])if dis < ret:ret = disa, b = i, jreturn ret, points[a], points[b]

最后是重头戏了,其实算法本身的代码量并不大,但是其中的细节不少,稍有不慎就可能翻车。

def divide_algorithm(points):n = len(points)# 特判只有一个点或者是两个点的情况if n < 2:return sys.maxsize, None, Noneelif n == 2:return distance(points[0], points[1]), points[0], points[1]# 对所有点按照横坐标进行排序points = sorted(points)half = (n - 1) // 2# 递归,这里有一个问题,为什么要先排序再递归?d1, a1, b1 = divide_algorithm(points[:half])d2, a2, b2 = divide_algorithm(points[half:])d, a, b = (d1, a1, b1) if d1 < d2 else (d2, a2, b2)calibration = points[half][0]# 根据中间的位置将点集分成两个部分left, right = [], []for u in points:if calibration - d < u[0] < calibration:left.append(u)elif calibration <= u[0] < calibration + d:right.append(u)# 右侧点集按照纵坐标排序right = sorted(right, key=lambda x: (x[1], x[0]))res = dfor u in left:# 左开右闭的二分l, r = -1, len(right)-1while r - l > 1:m = (l + r) >> 1if right[m][1] <= u[1] - d:l = melse:r = midx = r# 在范围内最多只有6个点能够构成最近点对for j in range(7):if j + idx >= len(right):breakif distance(u, right[idx + j]) < res:res = distance(u, right[idx + j])a, b = u, right[idx + j]return res, a, b

算法是实现完了,但是仍然有一些细节,比如说为什么我们在分治的时候需要先排序再递归呢?直接分成两个部分递归行不行?为什么不行?比如我们二分的时候使用的是左闭右开的区间二分?
这两个问题我先不给出答案,希望大家能够自己尝试着思考一下。

牛客网-数据结构笔试题目(六)-最近点对问题求解思路相关推荐

  1. 牛客网-数据结构笔试题目(五)-动态规划问题求解

    题意 给定n个整数,对于这n个整数我们可以采取两种操作.第一种操作是在数组左侧选择连续的k个整数减1,第二种操作是选择右侧的连续k个整数减1. 比如假设数组是[3, 2, 2, 1, 4],比如我们选 ...

  2. 牛客网-数据结构笔试题目(一)-猫咪特征提取思路解析(附源码)

    题意 小明是一名算法工程师,同时也是一名铲屎官.某天,他突发奇想,想从猫咪的视频里挖掘一些猫咪的运动信息.为了提取运动信息,他需要从视频的每一帧提取"猫咪特征".一个猫咪特征是一个 ...

  3. 牛客网-数据结构笔试题目(七)-k-amazing数字求解

    题意 给定n个数构成的数字,我们定义一个k-amazing数的概念.如果数a同时出现在数组中所有k个连续元素构成的序列当中,并且a是其中最小的那个,那么就称为a是一个k-amazing数字. 我们抽象 ...

  4. 牛客网-数据结构笔试题目(八)-离子能力跃迁问题求解

    题意 有一个人在玩一个离子激活的游戏,题目的背景是模拟的化学当中的离子能量跃迁.在化学当中,离子吸收能量可以从低能态跃迁到高能态,并且放出一定的能量. 现在有N粒离子排成一排(下标1-N),每一个离子 ...

  5. 牛客网-数据结构笔试题目(四)-Powerful Ksenia问题解决方案(附源码)

    题意 现在我们想要在n步这样的神奇异或操作之内让数组当中的所有元素全部相等,请问这一点是否可能呢?首先输出YES或NO,表示是否有解.如果有解输出需要操作的步数,以及对应选择的元素下标. 样例 在第一 ...

  6. 牛客网-数据结构笔试题目(三)-博弈论圆圈游戏(Circle Game)(附源码)

    题意 从前有两个人,一个叫Utkarsh,另外一个叫Ashish. 这两个人在一个2D的棋盘上玩移动棋子的游戏,一开始从原点出发,Ashish先手.每次可以把棋子向上或者是向右移动k个单位的距离.两人 ...

  7. 牛客网-数据结构笔试题目(二)-万万没想到之抓捕孔连顺思路解析(附源码)

    题意 我叫王大锤,是一名特工.我刚刚接到任务:在字节跳动大街进行埋伏,抓捕恐怖分子孔连顺.和我一起行动的还有另外两名特工,我提议 我们在字节跳动大街的N个建筑中选定3个埋伏地点. 为了相互照应,我们决 ...

  8. 牛客网校招题题目收集----数据结构与算法篇

    选择题 a,b,c,d,e 对应出现的频率为4,6,11,13,15:以下符合哈夫曼编码的选项是?() A. a=000.b=01.c=001.d=10.e=11 B. a=000.b=001.c=0 ...

  9. 爬虫实现爬取牛客网数据结构试题

    1 目标 爬取牛客网上关于<数据结构>的试题. 试题链接 进入网页可以看到,如果选择<数据结构>的某个知识点组卷,一次最多只能出30题. 因此,想法就是用程序一次将30题全部爬 ...

最新文章

  1. JavaScript之 Array(数组) 对象
  2. 信号归一化功率_UE低发射功率余量分析
  3. ABAP文档生成工具
  4. php下载 微信头像图片_php保存微信用户头像到本地或者服务器的完美方案!
  5. 开发指南专题三:JEECG微云快速开发平台项目编码规范
  6. Open3d之八叉树(Octree)
  7. Python案例实操1-网络爬虫
  8. rgb图像转换为二进制bin文件格式(matlab)
  9. 1994年联想大调整,杨元庆上位,能赚钱的“书呆子”倪光南却走了
  10. 补丁(patch)的制作与应用
  11. 【高速总线】JESD204B简介
  12. 收集的JS代码,学习js的入门经典
  13. PDF Expert for Mac 2.5.5 中文版 — PDF编辑工具
  14. JAVA-API(一)
  15. Ajax Interceptor工具分享
  16. android 高德地图动画,使用MotionLayout实现高德地图bottomSheets效果
  17. AppCompat V21:将 Materia Design 兼容到5.0之前的设备
  18. 免费文档翻译-免费批量文档翻译软件推荐
  19. linux下查看MBR实例分析
  20. 中国企业全球化、互联网+创新有捷径?Bespin Global提出“云”方案

热门文章

  1. 光端机怎样使用?光端机怎么和交换机连接?
  2. 工业以太网交换机常见故障排除的三种方法
  3. 交换机用光纤模块互连一端灯不亮或两端都不亮,如何处理?
  4. 【渝粤教育】国家开放大学2019年春季 2114人体解剖生理学 参考试题
  5. 【渝粤教育】国家开放大学2019年春季 1124流行病学 参考试题
  6. 基于Semtech LoRa SX1268 电路设计及PCB布局
  7. asynchttpclient 超时_dnf这才是混子的毕业套装,却发现超时空漩涡不买账!
  8. java 输入人名_Java 读取控制台输入
  9. html属性和dom属性的区别,HTML属性与DOM属性的区别?
  10. Python:递归输出斐波那契数列