分而治之—最近点对问题详解

其他平台网址导航:我的简书

最近点对问题,即平面上有n个点P1,P2,...,Pn,n>1,Pi的直角坐标为(Xi,Yi),i=1,2,...,n.求距离最近的两个点及他们之间的距离。

        对于这个问题,我们如果使用暴力求解的方法的话,n个点之间存在(n-1)*n/2个点对,将全部点对之间的距离计算出来并得到最短的距离,这个暴力算法将花费O(n^2)的时间,这个时间复杂度显然是不能让人接受的,接下来,我们使用分支的思想来解决这个问题。

       直接来推导平面中的最近点对是有点困难的,我们不妨先考虑一下直线的情况:即求出直线上的最近点对

                         这是一条直线

如上图所示:为了求出一条直线中距离最近的两个点,通过分治的思想,先将点根据大小进行排序,接下来我们可以在通过中点将直线划分为两部分,获得到S1区域的最小距离为d1,S2区域的最小距离为d2,d1,d2的计算我们可以通过递归实现,接下来考虑第三种情况:一个点在S1区域,一个点在S2区域,因为所有的点都在一条线段上,所以我们只要计算p3q3的距离d3,接下来就是获得最小值min=min{d1,d2,d3}。这样看来的话,对于一维情况下的最近点对,我们的时间复杂度为Olog(n)。

     接下来,我们就继续来看看二维中如何求得最近点对,对比一维,我们需要解决一下两个难点:

    一,如何将平面进行划分,使得我们可以用分治的思想去解决问题?

    二,如果划分好了S1,S2两部分区域后,如何解决一个点在S1,一个点在S2中的第三种情况呢?

关于第一个问题,如下图所示,我们可以在平面中间通过一条线段LS将平面划分为两个区域:

二维最近点对
二维情况下

我们先将平面中的点根据X轴进行排序,然后通过X轴的中点值进行划分,接下来递归到下一层,直到划分的区域中不能在划分为止

//这个就是递归终止条件:当区域中只剩下两个点甚至更少时,我们就计算出两个点之间的距离
if(head+1>=tail) {
	if(head==tail) {  //当区域为一个点时,这个时候将距离设为Double类型的最大值
		return Double.MAX_VALUE;
	}
     
	return CalculateUtil.getDistance(data[head].getX(), data[head].getY(), 
             data[tail].getX(), data[tail].getY());
}

 这样的话,我们就能获得到S1区域最小值d1,S2区域最小值d2

接下来我们就要考虑第三种情况了:当一个点存在S1区域,一个点存在S2区域时,我们怎么去获得d3呢,这个时候我们就需要了进行一个更细致地处理了:

在书籍《算法设计与分析》的分支策略中,关于最近点对问题,对上图有以下描述:(加上了一些本人的见解)

在P1区域任选一点坐标为(xi,yi),在右边窄缝内距Pi小于min(d1,d2) 其纵坐标一定在yi-min(d1,d2),yi+min(d1,d2)之间,话句话说,这些点只能在图片中的矩形区域中。书中又给了这样一个结论,对于每个点而言,检查另一边是否有点与他的距离小于min(d1,d2),只要考虑常数个数,这个常数不大于6,想要深入了解这个结论的由来的朋友可以了解一下(鸽巢原理

解决了这两个问题,接下来就是代码实现了,想要真正学会一个东西,就是去教会一个人,对于计算机领域而言,我们就是要去教会计算机如何去解决这个问题。

接下来是我自己实现的一份代码:(代码比较粗糙,在之后还会提供一份《Java语言程序设计(进阶篇)》第十版的最近点对问题解决代码)

首先是我的数据结构:Coordinates.java

/**
 * 坐标系
 *
 */
public class Coordinates  implements Comparable<Coordinates>{

	private Double x;  
	private Double y;
	public Double getX() {
		return x;
	}
	public void setX(Double x) {
		this.x = x;
	}
	public Double getY() {
		return y;
	}
	public void setY(Double y) {
		this.y = y;
	}
	public Coordinates(Double x, Double y) {
		super();
		this.x = x;
		this.y = y;
	}
	public Coordinates() {
		super();
	}
	
	@Override
	public String toString() {
		return "Coordinates [x=" + x + ", y=" + y + "]";
	}
	
	//通过重写该方法,可以通过Arrays.sort()通过对x值对本对象进行排序
	public int compareTo(Coordinates o) {
		 return new Integer(this.getX().compareTo(o.getX()));
	}

	
	
}

我的生成数据工具类:IOOpera.java

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Scanner;

/**
 * 生成用于测试的数据
 *
 */
public class IOOpera {
	
	private final static File DATA= new File("data.txt");

	/**
	 * 随机生成区间(a,b)中num个数据,并将数据写入到data.txt文件中
	 * @param a
	 * @param b
	 * @param num
	 * @throws IOException 
	 */
	public static void writeToFile(int a,int b,int num) throws IOException {
		PrintWriter out=null;
		try {
			

			out=new PrintWriter(DATA);
			
			//随机生成num个在(a,b)区间内的值
			for(int i=0;i<num;i++) {
				out.print((b-a)*Math.random()+a);  //输入x
				out.println(" "+Math.random()*200); //输入y
			}
			
			
		} catch (FileNotFoundException e) {
			System.out.println("找不到文件!");
			e.printStackTrace();
		}finally {
			if(null!=out) {
				out.close();
			}
		}
		
	}
	
	
	public static ArrayList<Coordinates> readFromFile() {
		Scanner in=null;
		
		ArrayList<Coordinates> coordinatesList=new ArrayList<Coordinates>();
		
		try {
			in=new Scanner(DATA);
			
			while (in.hasNext()) {
//				System.out.println("(X:"+in.nextDouble()+",Y:"+in.nextDouble()+")");
				coordinatesList.add(new Coordinates(in.nextDouble(),in.nextDouble()));
				
			}
		} catch (FileNotFoundException e) {
			
			e.printStackTrace();
		}finally {
			if(null!=in) {
				in.close();
			}
		}
		return  coordinatesList;
		
	}
	
	public static void main(String[] args) throws IOException {
		IOOpera.writeToFile(1, 1000, 200000);
		
		ArrayList<Coordinates> list = IOOpera.readFromFile();
		for (Coordinates coordinates : list) {
			System.out.println(CalculateUtil.mult(coordinates.getX(), coordinates.getY()));
		}
		
	}
}

我的计算类:CalculateUtil.java

/**
 * 
 * 计算工具类
 *
 */
public class CalculateUtil {

	//两数字想乘
	public static double mult(double a,double b) {
		return a*b;
	}
	
	
	//获得两点之间的距离
	public static double getDistance(double x1,double y1,double x2,double y2) {
		double distance=0;
		distance=Math.sqrt(mult((x2-x1), (x2-x1))+mult((y2-y1), (y2-y1)));
		
		return distance;
	}
	
	
	public static void main(String[] args) {
        //测试
		System.out.println(CalculateUtil.getDistance(3.7237733214685664, 21.833916865708613, 3.7384663016056487, 158.16628876345715));
	}
}

重中之重——我的算法类:ClosestPair .java

import java.util.ArrayList;
import java.util.Arrays;

public class ClosestPair {
	
	private static int[] temp;  //通过y值进行排序时使用
	
	//分治算法
	public static double getMin() {
		
		double min=Double.MAX_VALUE;
		ArrayList<Coordinates> list = IOOpera.readFromFile();
		//将ArrayList转换为[]
		Coordinates[] data=new Coordinates[list.size()];
		data=list.toArray(data);
		
		temp=new int[data.length];
		
		Arrays.sort(data);  //对数组进行排序
		
		min=getMinStance(0, data.length-1, data);
		return min;
	}
	
	
	public static double getMinStance(int head,int tail,Coordinates[] data) {
		double min=Double.MAX_VALUE;
		
		if(head+1>=tail) {
			if(head==tail) {
				return Double.MAX_VALUE;
			}
			return CalculateUtil.getDistance(data[head].getX(), data[head].getY(), data[tail].getX(), data[tail].getY());
		}
		
		int mid=(head+tail)/2;  //中点坐标
		
		double leftMin=getMinStance(head,mid,data);
		double rightMin=getMinStance(mid+1,tail,data);
		
		double d=leftMin<rightMin?leftMin:rightMin;  //d
		
		int k=0;
		//分离出宽度为d的区间
		for(int i=head;i<tail;i++) {
			if(Math.abs(data[mid].getX()-data[i].getX())<=d) {
				temp[k++]=i;
			}
		}
		
		/**将temp区域内的值根据y值进行排序
         * 之所以在这个地方要根据y进行排序的原因是:
         * 在接下来的嵌套for循环能够正常执行的前提就是y是排好序的
         */
		sortTemp(data);
		
		
		
		for(int i=0;i<k;i++) {
			for(int j=i+1;j<k&&Math.abs(data[temp[j]].getY()-data[temp[i]].getY())<d;j++) {
				double d3=CalculateUtil.getDistance(data[temp[i]].getX(), data[temp[i]].getY(),data[temp[j]].getX(), data[temp[j]].getY());
				if(d3<d) {
					d=d3;
//					System.out.println("P1点坐标("+data[temp[i]].getX()+ ","+data[temp[i]].getY()+") P2点坐标("+ data[temp[j]].getX()+","+data[temp[j]].getY()+")");
					
				}
			}
		}
		
		return d;
	}
	

	
	public static void sortTemp(Coordinates[] data) {
		int k=0;
        //一个冒泡排序 这部分可以改进 希望朋友可以实现一下
		while(true) {
			if(k>=temp.length) {
				break;
			}
			int j=0;
			Coordinates tem=null;
			while(j<k) {
				if(data[temp[j]].getY()>data[temp[j+1]].getY()) {
					tem=data[temp[j]];
					data[temp[j]]=data[temp[j+1]];
					data[temp[j+1]]=tem;
				}
				j++;
			}
			
			k++;
		}
	}
	
	public static void main(String[] args) {
		System.out.println(getMin());
	}
}

上述就是我所有解决问题的类,对于上述的类,有以下几点不足之处,

1.关于数据结构类没有设计好,使得在之后的数据处理中非常棘手

2.通过y值进行排序的算法本人使用了时间复杂度为O(n^2)的冒泡算法,这个是需要改进的

接下来载提供一份《Java语言程序设计(进阶篇)》第十版的最近点对问题解决代码:

import java.util.Arrays;

/** This program works, but the design is not good. Redesign it */
public class ClosestPair {
  // Each row in points represents a point
  private double[][] points;
  Point p1, p2;
  
  public static void main(String[] args) {
    double[][] points = new double[500][2];
    
    for (int i = 0; i < points.length; i++) {
      points[i][0] = Math.random() * 100;
      points[i][1] = Math.random() * 100;      
    }
    
    ClosestPair closestPair = new ClosestPair(points);
    System.out.println("shortest distance is " + 
      closestPair.getMinimumDistance());
    System.out.print("(" + closestPair.p1.x + ", " + 
      closestPair.p1.y + ") to ");
    System.out.println("(" + closestPair.p2.x + ", " + 
      closestPair.p2.y + ")"); 
  }

  ClosestPair() {
  }   
  
  public ClosestPair(double[][] points) {
    setPoints(points);
  } 
  
  public void setPoints(double[][] points) {
    this.points = points;
  }
  
  public double getMinimumDistance() {    
    Point[] pointsOrderedOnX = new Point[points.length];
    
    for (int i = 0; i < pointsOrderedOnX.length; i++)
      pointsOrderedOnX[i] = new Point(points[i][0], points[i][1]);
    
    Arrays.sort(pointsOrderedOnX);

    // Locate the identical points if exists
    if (checkIdentical(pointsOrderedOnX))
      return 0; // The distance between the identical points is 0
    
    Point[] pointsOrderedOnY = pointsOrderedOnX.clone();
    Arrays.sort(pointsOrderedOnY, new CompareY());
    
    return distance(pointsOrderedOnX, 0, 
        pointsOrderedOnX.length - 1, pointsOrderedOnY);
  }
  
  /** Locate the identical points if exist */
  public boolean checkIdentical(Point[] pointsOrderedOnX) {
    for (int i = 0; i < pointsOrderedOnX.length - 1; i++) {
      if (pointsOrderedOnX[i].compareTo(pointsOrderedOnX[i + 1]) == 0) {
        p1 = pointsOrderedOnX[i];
        p2 = pointsOrderedOnX[i + 1];
        return true;        
      }
    }
    
    return false;
  }

  /** Return the distance of the closest pair of points in 
   *  pointsOrderedOnY. 
   *  pointsOrderedOnX[low..high] and pointsOrderedOnY contain the 
   *  same points. */
  public double distance(
      Point[] pointsOrderedOnX, int low, int high,
      Point[] pointsOrderedOnY) {
    if (low >= high) // Zero or one point in the set
      return Double.MAX_VALUE;
    else if (low + 1 == high) {
      // Two points in the set
      p1 = pointsOrderedOnX[low];
      p2 = pointsOrderedOnX[high];
      return distance(pointsOrderedOnX[low], pointsOrderedOnX[high]);
    }

    // Divide the points into two sets in pointsOrderedOnX.
    // Store the points p into pointsOrderedOnYL if 
    //   p.x <= pointsOrderedOnX[mid]. Otherwise, store it into
    // pointsOrderedOnYR. The points in pointsOrderedOnYL and 
    // pointsOrderedOnYL are ordered on their y-coordinates    
    int mid = (low + high) / 2;
    Point[] pointsOrderedOnYL = new Point[mid - low + 1];
    Point[] pointsOrderedOnYR = new Point[high - mid];
    int j1 = 0; int j2 = 0;
    for (int i = 0; i < pointsOrderedOnY.length; i++) {
      if (pointsOrderedOnY[i].compareTo(pointsOrderedOnX[mid]) <= 0)
        pointsOrderedOnYL[j1++] = pointsOrderedOnY[i];
      else
        pointsOrderedOnYR[j2++] = pointsOrderedOnY[i];
    }

    // Recursively find the distance of the closest pair in the left
    // half and the right half
    double d1 = distance(
      pointsOrderedOnX, low, mid, pointsOrderedOnYL);
    double d2 = distance(
      pointsOrderedOnX, mid + 1, high, pointsOrderedOnYR);
    double d = Math.min(d1, d2);

    // stripL: the points in pointsOrderedOnYL within the strip d
    int count = 0;
    for (int i = 0; i < pointsOrderedOnYL.length; i++)
      if (pointsOrderedOnYL[i].x >= pointsOrderedOnX[mid].x - d)
        count++;
    Point[] stripL = new Point[count];
    count = 0;
    for (int i = 0; i < pointsOrderedOnYL.length; i++)
      if (pointsOrderedOnYL[i].x >= pointsOrderedOnX[mid].x - d)
        stripL[count++] = pointsOrderedOnYL[i];

    // stripR: the points in pointsOrderedOnYR within the strip d
    count = 0;
    for (int i = 0; i < pointsOrderedOnYR.length; i++)
      if (pointsOrderedOnYR[i].x <= pointsOrderedOnX[mid].x + d)
        count++;
    Point[] stripR = new Point[count];
    count = 0;
    for (int i = 0; i < pointsOrderedOnYR.length; i++)
      if (pointsOrderedOnYR[i].x <= pointsOrderedOnX[mid].x + d)
        stripR[count++] = pointsOrderedOnYR[i];

    // Find the closest pair for a point in stripL and 
    // a point in stripR
    double d3 = d;
    int j = 0;
    for (int i = 0; i < stripL.length; i++) {
      while (j < stripR.length && stripL[i].y > stripR[j].y + d)
        j++;

      // Compare a point in stripL with six points in stripR
      int k = j; // Start from r1 up in stripR
      while (k < stripR.length && stripR[k].y <= stripL[i].y + d) {
        if (d3 > distance(stripL[i], stripR[k])) {
          d3 = distance(stripL[i], stripR[k]);
          p1 = stripL[i];
          p2 = stripR[k];
        }
        k++;
      }
    }

    return Math.min(d, d3);
  }

  /** Compute the distance between two points p1 and p2 */
  public static double distance(Point p1, Point p2) {
    return distance(p1.x, p1.y, p2.x, p2.y);
  }

  /** Compute the distance between two points (x1, y1) and (x2, y2) */
  public static double distance(
      double x1, double y1, double x2, double y2) {
    return Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1));
  }

  /** Define a class for a point with x- and y- coordinates */
  static class Point implements Comparable<Point> {
    double x;
    double y;

    Point(double x, double y) {
      this.x = x;
      this.y = y;
    }

    public int compareTo(Point p2) {
      if (this.x < p2.x)
        return -1;
      else if (this.x == p2.x) {
        // Secondary order on y-coordinates
        if (this.y < p2.y)
          return -1;
        else if (this.y == p2.y)
          return 0;
        else
          return 1;
      } 
      else
        return 1;
    }
  }
  
  /** A comparator for comparing points on their y-coordinates. 
   * If y-coordinates are the same, compare their x-coordinator. */
  static class CompareY implements java.util.Comparator<Point> {
    public int compare(Point p1, Point p2) {
      if (p1.y < p2.y)
        return -1;
      else if (p1.y == p2.y) {
        // Secondary order on x-coordinates
        if (p1.x < p2.x)
          return -1;
        else if (p1.x == p2.x)
          return 0;
        else
          return 1;
      } 
      else
        return 1;
    }
  }
}

 

  • 5
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值