如何从二维平面n个点中寻找距离最近两个点?

如何理解分治算法

     什么是分治算法?简单来说就是“分而治之”,也就是将原问题划分成n个规模较小的,并且结构与原问题相似的子问题,然后去递归地解决这些子问题,最后再合并其结果,就得到原问题的解。

     对于分治算法来说,一般适合用递归来实现。分治算法的递归实现中,每一次递归都会涉及如下三个操作。

  • 分解:将原问题分解成一系列子问题。
  • 解决:递归地求解各个子问题。
  • 合并:将子问题的结果合并成原问题。

分治算法的问题模型

      对于可以采用分治算法来解决的问题,一般都需要满足以下几个条件:

  • 原问题与分解成的子问题具有相同的模式。
  • 原问题分解成的子问题可以独立求解,子问题之间没有相关性。
  • 具有分解终止条件,也就是说,当问题足够小时,可以直接求解。
  • 可以将子问题合并成原问题,而这个合并操作的复杂度不能太高,否则就起不到减小算法总体复杂度的效果了。

    下面我们来结合一个例子来看一下分治算法的应用。我们来思考这么一个问题,在给定一组在二维空间中的n个点,如何快速找出距离最近的两点呢?最直观的想法就是通过遍历所有的点,然后求出所有点之间的距离,最后选出这些距离中的最小值对应的点,但是这种算法的时间复杂度是O(n*n),那有没有更快的方法呢?那就是分治算法。

为了方便起见,我们把N设置为2的k次幂,即N=2^k,k为正整数。下面我们来看一下这个问题是否满足分治算法的问题模型。

     我们可以把问题N拆分成两个子问题,每个子问题等于原问题的一半。我们在二维平面中画一条垂线e,正好把N个点按照x轴位置拆分成2半(这个过程需要按照x轴排序,取中间的点),使得e的左右两边都有n/2个点。假设我们对e的左半边部分递归的求出的最近点对的距离为ld,e的右半边部分递归的求出的最近点对的距离为rd,接下来我们取出这两个距离中的最小值d=min(ld,rd),并且在e-d和e+d的位置上画两条垂线。这样一来,我们把二维空间划分成了3部分。如下图所示:

图片

   这样划分后,最近点对的出现位置只能有以下三种可能。

  1. 两个节点都在左边区域。
  2. 一个节点在左,一个节点在右。
  3. 两个节点都在右边。

    其中,1和3我们已经在求解子问题的时候已经解决了,现在我们只需要求解第2个问题就好了,即在mid区域进行搜索。我们下面来看如何在mid区域进行搜索,我们需要把这个带状区域内的所有节点按照Y轴递增排序(我们可以在初始化的时候就缓存起来,后面直接使用就好),从Y排序最小的节点开始,我们连续检查带状区域内的每个点,计算所有比它的Y轴更大的点之间的距离,尝试找出距离比d更小的点对。

     假设我们需要对节点p进行比较,我们考虑这样的一个空间,他是通过8个正方形(d/2*d/2)组成的一个长方形,p节点位于这个长方形的底边上,

我们只需要判断这个区域内的所有节点即可,因为一旦Y轴差距超过了d,就算x轴之差为0也会大于我们之前的距离d,所以不可能找出比 d 距离更小的点对。并且我们针对一个点,事实上只需要最多对比 7 次就能找出所有可能小于 d 的点对,因为每个小格子内部只可能出现1个节点,因为小格子内部的极限距离为 d / sqrt(2),也就是正方形的对角线,但是这个距离显然小于 d,如果这个正方形内部存在这样一个节点。那么之前对于 d 是左右区域内最小的点对距离的定义就被破坏了。所以不可能存在。

图片

图片

        综上所述:这个问题是符合分治算法的问题模型的。下面我们来看一下代码实现。

`

import math
class Point:
     def __init__(self,x,y):
          self.x=x
          self.y=y

     def __str__(self):
          return 'x='+str(self.x)+',y='+str(self.y)


class RecentPoint:
     sortByXPoints = []
     sortByYPoints = []
     # p1=None
     # p2=None

     def getDistance(self, p1, p2):
          xDis = (p1.x - p2.x) ** 2
          yDis = (p1.y - p2.y) ** 2
          return math.sqrt(xDis + yDis)
     def findRecentPoint(self,data):
          self.sortByXPoints=sorted(data,key=lambda point:point.x)
          self.sortByYPoints=sorted(data,key=lambda point:point.y)

          return self._findRecentPoint(0, len(data)-1)

     def _findRecentPoint(self, p, q):

          #区域内只有两对节点
          if (q-p)<=1:
               return self.getDistance(self.sortByXPoints[p], self.sortByXPoints[q])

          middle=math.floor((p+q)/2)

          ld=self._findRecentPoint(p, middle)
          rd=self._findRecentPoint(middle+1, q)

          # if(ld<rd):
          #      d=ld
          #      self.p1 = self.sortByXPoints[p]
          #      self.p2 = self.sortByYPoints[middle]
          # else:
          #      d = rd
          #      self.p1 = self.sortByXPoints[middle+1]
          #      self.p2 = self.sortByYPoints[q]

          d=min(ld,rd)

          #中心点
          e=self.sortByXPoints[middle].x + (self.sortByXPoints[middle+1].x - self.sortByXPoints[middle].x) / 2

          LeftEdge = e - d
          RightEdge = e + d

          #接下来我们检查已 X 轴坐标 e 为中心点 从 e - d 开始 e + d 结束的带状区域内去检测最近点
          #我们从中筛选所有带状区域内的点,并按照 Y坐标 的递增排序进行排序

          insidePoint=[]
          for point in self.sortByYPoints:
               if(point.x>LeftEdge and point.x<RightEdge):
                    insidePoint.append(point)

          #开始对比节点,寻找是否比d更短
          for i in range(len(insidePoint)):
               for j in range(1,8):
                    if(i+j>=len(insidePoint)):
                         break;
                    dis=self.getDistance(insidePoint[i],insidePoint[i+j])

                    if(dis<d):
                         d=dis
                         # self.p1=insidePoint[i]
                         # self.p2=insidePoint[i+j]


          return d

data=[Point(1,6),Point(3, 4),Point(2, 5),Point(4, 8)]
s=RecentPoint()
print(s.findRecentPoint(data))
# print(s.p1)
# print(s.p2)
复制代码

` 今天的分享就到这里,更多硬核知识,请关注公众号,如果需要pdf文件,可以私信我哦。

 

 

 

posted @ 2021-08-09 18:22  公众号程序员学长  阅读(1777)  评论(0编辑  收藏  举报