分治法在求解“最近对”问题中的应用
最近对问题在蛮力法中有过讲解,时间复杂度为O(n^2),下面将会采用分治法讲解这类问题,时间复杂度会降到O(nlogn)
我们将笛卡尔平面上n>1个点构成的集合称为P。若2<= n <= 3时,我们1可以通过蛮力法求解。但当n>3时,采用分治法或许是个更好的选择。假设这些点是按照x轴、y轴升序排列的,可以找出点集在x轴方向上的中位数m,做一条垂直x轴的分割线,由此点将点集划分为左右两个大小为n/2的子集P1和P2,之后通过递归求解出在子集中的最近对距离d1,d2,最后找出d=max{d1,d2}。
但是!!!不巧的是,我们忽略了一个问题,如果距离最近的两个点刚好分别在两个子集中,那么d就不是所有点对的最小距离。我们需要在每次合并子问题结果时,要加以判断是否存在这样的点对。方法是:只考虑以分割线为对称轴、宽度为2d的垂直带中的的点,因为其他点对的距离都是大于d的。
这里给出一个优化,当我们在垂直带中找到一个点p,只需要考虑p之后的5个点即可。
这是因为:如果我们在垂直带中找到p-p'两点的距离小于p,由于我们的序列时经过排序的,所以p'一定在p之后,且两点在y轴上的距离一定是小于d的(根据勾股定理,两点之间的距离如果小于d,那么x轴分量和y轴分量都是小于d的,反之,不可能存在这个点)。所以在几何学上,p'的位置一定在下图中的淡黄色矩形区域。而矩形区域内一般只能包含少量的候选点,这个数量最大为6(根据鸽巢定理)。图中6个红色点为极端的临界情况。我们将d * 2d的矩形划分为d/2 * 2d/3的6块区域,如果超过6个点,假设为7,那么一定会出现某个小矩形中有两个点,这两个点的最大距离为图中红线距离5/6d <d,这和d的意义不符。
import java.util.Arrays;
import java.util.Comparator;
import java.util.Scanner;class Point {double x;double y;Point (double x, double y) {this.x = x;this.y = y;}
}
public class Main {static Point[] point;static Point[] minP = new Point[2];static Scanner in = new Scanner(System.in);public static void main(String[] args) {int n = in.nextInt();point = new Point[n];
// for (int i = 0; i < n; i++) {
// int a = in.nextInt();
// int b = in.nextInt();
// point[i] = new Point(a, b);
// }point[0] = new Point(1,3);point[1] = new Point(2,1);point[2] = new Point(3,5);point[3] = new Point(4,4);point[4] = new Point(5,2);Arrays.sort(point,0, n, new Comparator<Point>() {@Overridepublic int compare(Point o1, Point o2) {return (int) (o1.x - o2.x);}});System.out.println(point.length);double minD = closestPoint(0, point.length-1);for (int i = 0; i < 2; i++) {System.out.println(minP[i].x + "," + minP[i].y);}System.out.println(minD);}private static double closestPoint(int low, int high) {Point[] temp1 = new Point[2];Point[] temp2 = new Point[2];Point[] p = new Point[high - low + 1];double d, d1, d2, d3;int index = 0;if (high - low == 1) {minP[0] = new Point(point[low].x, point[low].y);minP[1] = new Point(point[high].x, point[high].y);return distance(point[low], point[high]);}if (high - low == 2) {d1 = distance(point[low], point[low+1]);d2 = distance(point[low+1], point[high]);d3 = distance(point[low], point[high]);if ((d1 <= d2) && (d1 <= d3)) {minP[0] = new Point(point[low].x, point[low].y);minP[1] = new Point(point[low+1].x, point[low+1].y);return d1;} else if (d2 <= d3) {minP[0] = new Point(point[low+1].x, point[low+1].y);minP[1] = new Point(point[high].x, point[high].y);return d2;} else {minP[0] = new Point(point[low].x, point[low].y);minP[1] = new Point(point[high].x, point[high].y);return d3;}}int mid = (low + high) / 2;d1 = closestPoint(low, mid);temp1[0] = minP[0];temp1[1] = minP[1];d2 = closestPoint(mid+ 1, high);temp2[0] = minP[0];temp2[1] = minP[1];if (d1 < d2) {d = d1;minP[0] = temp1[0];minP[1] = temp1[1];} else {d = d2;minP[0] = temp2[0];minP[1] = temp2[1];}for (int i = mid;i>=low && (point[mid].x - point[i].x) < d; i--) {p[index++] = point[i];}for (int i = mid+1;i<=high && (point[i].x - point[mid].x) < d; i++) {p[index++] = point[i];}Arrays.sort(p, 0, index, new Comparator<Point>() {@Overridepublic int compare(Point o1, Point o2) {return (int) (o1.y - o2.y);}});for (int i = 0; i < index-1; i++) {for (int j = i+1; j < index; j++) {if ((p[j].y - p[i].y) >= d) {break;} else {d3 = distance(p[i], p[j]);if (d3 < d) {minP[0] = new Point(p[i].x, p[i].y);minP[1] = new Point(p[j].x, p[j].y);}}}}return d;}private static double distance(Point p1, Point p2) {return Math.sqrt(Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2));}
}
Input:
5
Output:
4.0,4.0
3.0,5.0
1.4142135623730951