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

如果觉得再简述上阅读代码太困难可以点这里:最近点对问题

最近点对问题,即平面上有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;

    }

  }

}

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,122评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,070评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,491评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,636评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,676评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,541评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,292评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,211评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,655评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,846评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,965评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,684评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,295评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,894评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,012评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,126评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,914评论 2 355

推荐阅读更多精彩内容

  • SwiftDay011.MySwiftimport UIKitprintln("Hello Swift!")var...
    smile丽语阅读 3,837评论 0 6
  • 贪心算法 贪心算法总是作出在当前看来最好的选择。也就是说贪心算法并不从整体最优考虑,它所作出的选择只是在某种意义上...
    fredal阅读 9,231评论 3 52
  • created by Dejavu(不断更新中) 简介 地面信息的提取对于车形的智能机器人来说十分重要,之前一直采...
    ericdejavu阅读 1,087评论 0 1
  • //出自51博客:www.Amanda0928.51.com 第一章 一、选择题 1.B; (typedef ,t...
    Damongggggg阅读 11,130评论 0 1
  • 你面前的小丑 嬉笑搞怪 你笑他幼稚可爱 不知他是被控制的面具 其后的真颜 未曾显现 他不敢 将那 丑陋、虚伪、阴暗...
    沈昕啦啦啦阅读 526评论 0 0