最近点对问题

问题描述:
  在平面上有点 p1(x1,y1),p2(x2,y2),……,pn(xn,yn)包含于集合S。找出S中距离(一般是欧氏距离)最近的点对。最近点对可能不止一对。
问题分析:
  该问题比较简单直观的做法是穷举法,直接两层循环计算所有的点对之间的距离,并用map存储点对的距离,同时找到最小距离。最后再遍历一次map找到距离为最小值的所有点对。时间复杂度为T(n) = O(n^2)+O(n^2) = O(n^2),空间复杂度为O(n^2)。
 
  该问题也可以用分治法解决,核心思想就是把集合划分为相等的两部分S1和S2,找到S1和S2中各自最小距离d1和d2,再考虑S1和S2各有一个点的情况。如下图所示:
  
 
  令d = min(d1,d2)。如果最小距离点对是在S1和S2各有一个。则横轴必定在[-d,d]的矩形范围内,因此只需要找到S1和S2这个范围内的点,将矩形范围两边的点两两计算距离再比较一次就行。
  
  其实还可以进一步减小比较的范围,对于矩形左侧范围内的任意一点,如果右侧存在满足条件的另一个点,则一定在宽为d长为2d的范围内。由d的定义可知该区域内最多只有五个点存在。若是将矩形范围内所有点按y值顺序排序,则只需要比较y值大于等于当前点的至多五个点(己方也可能有,判断的时候判断是否是异侧的点)。该阶段时间复杂度由O(n^2)减少到O(nlgn)。
  
 
算法描述:
  1.输入集合S中的n个点的坐标{(x1,y1)...(xn,yn)};
  2.判断n值
    2.1 如果n==1,直接最大距离;
    2.2 如果n==2,直接计算两个点的距离并返回两个点;
    2.3 如果n>3,将集合划分为两个部分S1,S2(如果是根据x值排序好的,取中间序号为分界线就可);
      2.3.1 将S1输入步骤1,计算S1中的最小距离d1以及对应点对;
      2.3.2 将S2输入步骤1,计算S2中的最小距离d2以及对应点对;
      2.3.3 计算d = min(d1,d2),依次考察集合S中的点p(x,y),如果(x<=xm+d 并且x>=xm-d),则将点p放入新的集合中;
      2.3.4将集合中的点按y值排序,对于任一点(x,y),最多只需要比较序号之后五位即可。
  3 返回min(d,d3);
java版本代码实现(含注释):
  1 // 点对以及两个点的距离
  2 public class Union {
  3     double x1;
  4     double y1;
  5     double x2;
  6     double y2;
  7     double dis;
  8     public Union(double x1, double y1, double x2, double y2, double dis) {
  9         // 构造的时候按点的顺序构造
 10         if (x1 > x2 || (x1 == x2 && y1 > y2)) {
 11             this.x1 = x2;
 12             this.y1 = y2;
 13             this.x2 = x1;
 14             this.y2 = y1;
 15             this.dis = dis;
 16         }
 17         else {
 18             this.x1 = x1;
 19             this.y1 = y1;
 20             this.x2 = x2;
 21             this.y2 = y2;
 22             this.dis = dis;
 23         }
 24     }
 25 }
 26 // 计算两个点的距离
 27 public double distance(double x1, double y1, double x2, double y2) {
 28     return Math.sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2));
 29 }
 30 // 计算points[l]-points[r]的集合中最小距离点对,输入的points已经按x升序排序
 31 public List<Union> findMinimum (double[][] points, int l, int r) {
 32     List<Union> res = new ArrayList<>();
 33     // 如果n == 1,返回最大距离
 34     if (l == r) {
 35         Union union = new Union(0,0,0,0,Double.MAX_VALUE);
 36         res.add(union);
 37     }
 38     // 如果n == 2,返回点对和距离
 39     else if (r == l+1) {
 40         double dis = distance(points[l][0], points[l][1], points[r][0], points[r][1]);
 41         Union union = new Union(points[l][0], points[l][1], points[r][0], points[r][1], dis);
 42         res.add(union);
 43     }
 44     // 如果n>=3
 45     else {
 46         // 这里points已经按x升序排序,所以可以直接取中间的点分隔为S1,S2
 47         int mid = (l+r)>>1;
 48         // 获取S1和S2的最小距离点对
 49         List<Union> list1 = findMinimum(points, l, mid);
 50         List<Union> list2 = findMinimum(points, mid+1, r);
 51         // 因为n>=3,所以list1和list2都必定不为空,get(0)一定有元素
 52         double d = Math.min(list1.get(0).dis, list2.get(0).dis);
 53         List<Union> list3 = new ArrayList<>();
 54         // 找到位于中间矩形区域内的点,加入到新集合中
 55         List<double[]> list = new ArrayList<>();
 56         for (int i = l; i <= r; i++) {
 57             if (Math.abs(points[i][0] - points[mid][0]) <= d)
 58                 list.add(new double[]{points[i][0],points[i][1],i});
 59         }
 60         // 对新集合按y值排序
 61         Collections.sort(list, (p1, p2) -> {
 62             if (p1[1] > p2[1])    return 1;
 63             else return -1;
 64         });
 65         // 对集合的每个点,只要最多找序号之后五个点比较距离即可(同时要判断是否是异侧的点)
 66         for (int i = 0; i < list.size()-1; i++) {
 67             int k = i+6 > list.size() ? list.size() : i+6;
 68             for (int j = i+1; j < k; j++) {
 69                 // 如果是异侧
 70                 if ((list.get(i)[2] <= mid && list.get(j)[2] > mid) || (list.get(i)[2] > mid && list.get(j)[2] <= mid)) {
 71                     double dis = distance(list.get(i)[0],list.get(i)[1],list.get(j)[0],list.get(j)[1]);
 72                     if (dis <= d) {
 73                         if (list3.size() == 0)
 74                             list3.add(new Union(list.get(i)[0],list.get(i)[1],list.get(j)[0],list.get(j)[1],dis));
 75                         else {
 76                             if (dis < list3.get(0).dis) {
 77                                 list3.clear();
 78                                 list3.add(new Union(list.get(i)[0],list.get(i)[1],list.get(j)[0],list.get(j)[1],dis));
 79                             } else if (dis == list3.get(0).dis) {
 80                                 list3.add(new Union(list.get(i)[0],list.get(i)[1],list.get(j)[0],list.get(j)[1],dis));
 81                             }
 82                         }
 83                     }
 84                 }
 85             }
 86         }
 87         // 判断list1,list2,list3的dis大小关系
 88         if (list3.size() > 0 && list3.get(0).dis < d)
 89             return list3;
 90         else {
 91             if (list1.get(0).dis == d) {
 92                 for (Union union : list1)
 93                     res.add(union);
 94             }
 95             if (list2.get(0).dis == d) {
 96                 for (Union union : list2)
 97                     res.add(union);
 98             }
 99             for (Union union : list3)
100                 res.add(union);
101         }
102     }
103     return res;
104 }

 

posted @ 2021-01-29 22:31  凝冰物语  阅读(229)  评论(0编辑  收藏  举报