贪心算法-最近点对问题

这个问题真的很有意思,给定空间上的n个节点S={(xi,yi)},如何查找这n个点对中最近的点对的距离? 我们都知道两点间距离:((xi-xj)²+(yi-yj)²)1/2
那么如果使用暴力搜索,需要两两检测,需要花费O(N²).
我们可以采用分治法的思想.首先我们假设这些点都已经按照x坐标排序过,那么可以在中间画一条线,把点集分为Pl和Pr,那么最近的一对点要么都在Pl中,要么都在Pr中,要么分别在Pl和pr中,把这三个距离分别用dl,dr,dc表示.

这里写图片描述

显然我们可以递归第计算dl和dr,关键是计算dc.令δ=min(dl,dr),显然我们可以做出双道带,来缩小考虑的范围

这里写图片描述

对于在带中的,我们可以通过两层循环蛮力计算,但毕竟还是不好,最坏的情况是说有点都可能在这带状区域里.那么考虑对y坐标排序.如果pi和pj的y坐标差大于δ,那么可以直接break跳出内循环.如对于p3来说,我们只需要考虑p4和p5,之后的break就行了.

这里写图片描述

还有一点可优化的是,其实对于任意的点pi,最多有7个点需要被考虑.

这里写图片描述

如图所示,这是点最多的情况.因为如果在上图的情况中随便再添一个点,比如在左边,那么这个点距离左边其他四个点的距离肯定有小于δ的(最均匀矩形中心,距离小于δ).那么只要距离一个点小于δ.那么与之前的假设在Pl中最短的距离为δ矛盾了!所以我们只需考虑最坏情况,某个角上是pi,那么其余还有7个点要考虑,得证.
接下来就容易了,但是还有要注意的是,不能每次递归都去排序x,y坐标.可以保留两个表,一个x坐标排序的表,一个y坐标排序的表.这个算法是O(NlogN)的.
还是来实现吧:

import java.util.ArrayList;
import java.util.Set;
import java.util.TreeSet;
public class Distance {
 static final int NUM=10;//使用穷举法的点数   
 public static void main(String[] args) {
     Set<Point> testData = new TreeSet<Point>();         
     java.util.Random random = new java.util.Random();  
     for(int i = 0;i < 100000;i++){  
         int x = random.nextInt(100000);  
         int y = random.nextInt(100000);  
         testData.add(new Point(x, y));  
     } 
     Point [] points = new Point[testData.size()];  
     points = (Point[]) testData.toArray(points);
     Point[] result=new Point[2];
     long startTime=System.currentTimeMillis();   //获取开始时间
     result=findpair(points);
     long endTime=System.currentTimeMillis(); //获取结束时间
     System.out.println("最近点: ("+result[0].getX()+","+result[0].getY()
             +")--("+result[1].getX()+","+result[1].getY()+")");
     System.out.println("距离为: "+distance(result[0], result[1]));
     System.out.println("分治法运行时间:"+(endTime-startTime)+"ms");
     long startTime2=System.currentTimeMillis();   //获取开始时间
     result=bong(points);
     long endTime2=System.currentTimeMillis(); //获取结束时间
     System.out.println("穷举法运行时间:"+(endTime2-startTime2)+"ms");
 }    
 //寻找最近点对
 public static Point[] findpair(Point[] p){
     Point[] result=new Point[2];
     if(p.length<NUM)
         return bong(p);
     //开始画线了  求所有点在x坐标的中位数
     int minX = (int) Double.POSITIVE_INFINITY; 
     int maxX = (int) Double.NEGATIVE_INFINITY;
     for(int i = 0; i < p.length; i++){  
         if(p[i].getX() < minX)  
             minX = (int) p[i].getX();  
         if(p[i].getX() > maxX)  
             maxX = (int) p[i].getX();  
     }  
     int midX = (minX + maxX)/2;
     //把以midx为界划分出的点分成两组放到两个表
     ArrayList<Point> L1=new ArrayList<Point>();
     ArrayList<Point> L2=new ArrayList<Point>();
     for(int i = 0; i < p.length; i++){  
         if(p[i].getX() <= midX)       
             L1.add(p[i]);  
         if(p[i].getX() > midX)  
             L2.add(p[i]);  
     } 
     //按x坐标排序
     Point [] p1 = new Point[L1.size()];  
     Point [] p2 = new Point[L2.size()];           
     L1.toArray(p1);  
     L2.toArray(p2);  
     mergeSort(p1, "x");     //按X坐标升序排列  
     mergeSort(p2, "x");     //按X坐标升序排列 
     //递归求p1,p2中最近的两个点
     Point[] result1 = new Point[2];  
     result1 = findpair(p1);
     Point[] result2 = new Point[2];  
     result2 = findpair(p2);
     //求二者中的最小值
     if (distance(result1[0], result1[1])<distance(result2[0], result2[1])) {
         result=result1;
     }else {
         result=result2;
     }
     double distance=Math.min(distance(result1[0], result1[1]),distance(result2[0], result2[1]));
     //开始划分带了  在两个子集中找哪些距离划分线小于d的保存
     ArrayList<Point> L3 = new ArrayList<Point>();   
     for(int i = 0; i < p1.length; i++){  
         if(midX - p1[i].getX() < distance)  
             L3.add(p1[i]);  
     }  
     for(int i = 0; i < p2.length; i++){  
         if(p2[i].getX() - midX < distance){  
             L3.add(p2[i]);  
         }  
     } 
     //将得到的按照y升序排列
     Point [] p3 = new Point [L3.size()];  
     L3.toArray(p3);            
     mergeSort(p3, "y");  
     //然后开始优化的穷举 即比较之后的7个点
     if (p3.length<NUM) {
         Point[] temp= bong(p3);
         if (distance(temp[0], temp[1])<distance&&distance(temp[0], temp[1])!=0) {
             result=temp;
         }
     }else {
         for(int i=0;i<p3.length-7;i++){
             double tempd;
             for(int j=1;j<8;j++){
                 if (i+j>=p3.length) {
                     break;
                 }else {
                     tempd=distance(p3[i], p3[i+j]);
                     if (tempd<distance&tempd!=0) {
                         result[0]=p3[i];
                         result[1]=p3[i+j];
                     }
                 }                 
             }
         }
     }
     return result;
 }    
 //归并排序
 private static void mergeSort(Point[] p, String flag) {
     Point[] result = new Point[p.length];  
     mergeSort(p, result, 0, p.length - 1, flag); 
 }
 private static void mergeSort(Point[] a, Point [] result, int left, int right, String flag){  
     if(left < right){  
         int center = (left + right) >> 1;  
         //分治  
         mergeSort(a, result, left, center, flag);  
         mergeSort(a, result, center + 1, right, flag);  
         //合并  
         merge(a, result, left, center + 1, right, flag);  
     }  
   } 
 private static void merge(Point [] a, Point [] result, int leftPos, int rightPos, int rightEnd, String flag){  
     int leftEnd = rightPos - 1;  
     int numOfElements = rightEnd - leftPos + 1;  

     int tmpPos = leftPos;       //游标变量, 另两个游标变量分别是leftPos 和 rightPos            
     while(leftPos <= leftEnd && rightPos <= rightEnd){  
         if(flag.equals("x")){  
             if(a[leftPos].getX() <= a[rightPos].getX())  
                 result[tmpPos++] = a[leftPos++];  
             else  
                 result[tmpPos++] = a[rightPos++];  
         }else if(flag.equals("y")){  
             if(a[leftPos].getY() <= a[rightPos].getY())  
                 result[tmpPos++] = a[leftPos++];  
             else  
                 result[tmpPos++] = a[rightPos++];  
         }else  
             throw new RuntimeException();  
     }       
     while(leftPos <= leftEnd)  
         result[tmpPos++] = a[leftPos++];  
     while(rightPos <= rightEnd)  
         result[tmpPos++] = a[rightPos++];         
     //将排好序的段落拷贝到原数组中  
     System.arraycopy(result, rightEnd-numOfElements+1, a, rightEnd-numOfElements+1, numOfElements);  
 }      
 //穷举法
 private static Point[] bong(Point[] p){
     Point[] result=new Point[2];
     if (p.length<=1) {
         result[0]=new Point(Double.MIN_VALUE, Double.MIN_VALUE);
         result[1]=new Point(Double.MAX_VALUE, Double.MAX_VALUE);
         return result;
     }else{
         double min=distance(p[0], p[1]);
         int start=0;
         int end=1;
         for (int i = 0; i < p.length; i++) {
             for (int j = i+1; j < p.length; j++) {
                 if (distance(p[i], p[j])<min&&distance(p[i], p[j])!=0) {
                     min=distance(p[i], p[j]);
                     start=i;
                     end=j;
                 }
             }
         }
         result[0]=p[start];
         result[1]=p[end];
         return result;
     }
 }    
 //计算距离
 private static double distance(Point p1,Point p2){
     return Math.sqrt((p1.getX()-p2.getX())*(p1.getX()-p2.getX())+(p1.getY()-p2.getY())*(p1.getY()-p2.getY()));
 }
 //用一个类来表示点
 static class Point implements  Cloneable,Comparable<Point>{
     double x,y;
     public Point(double x, double y) {
         super();
         this.x = x;
         this.y = y;
     }
     public double getX() {
         return x;
     }
     public double getY() {
         return y;
     }
     public int compareTo(Point o) {  
         if(x == o.getX() && y == o.getY())  
             return 0;  
         else   
             return 1;  
     }
     public boolean equals(Object p) {
         // TODO 自动生成的方法存根
         if (this.x==((Point) p).getX()&&this.y==((Point) p).getY()) {
             return true;
         }else {
             return false;
         }
     }
 }    
}

我们采用了随机数产生器随机产生点,并统计了分治法与纯粹暴力搜索的运行时间,差距相当明显(这个程序当规模特别大的时候还是会栈溢出,为啥)

这里写图片描述

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值