分治法在求解“最近对”问题中的应用
最近对问题在蛮力法中有过讲解,时间复杂度为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>() {
@Override
public 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>() {
@Override
public 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