问题描述
给定平面S上n个点,找其中的一对点,使得在n个点组成的所有点对中,该点对间的距离最小
蛮力法求解
点的存储:
class Point{
//横坐标
int x;
//纵坐标
int y;
/**
* 求解两个坐标之间的距离
* @return
*/
public static double distance(Point ri, Point le) {
return Math.sqrt((ri.x - le.x)*(ri.x - le.x) + (ri.y - le.y)*(ri.y - le.y));
}
}
求解过程:
/**
* 使用蛮力法求解最短点距
* @param list 点列表
* @param begin 开始索引
* @param end 结束索引
* @return 最短点距
*/
private static Object[] simDistance(Point[] list, int begin, int end) {
//存储最近点对及距离
Object[] result = new Object[3];
result[0] = Double.MAX_VALUE;
for(int i = begin; i <= end; i++) {
for(int j= i+1; j <= end; j++) {
//求解每一对点的距离
double distance = Point.distance(list[i],list[j]);
if((double)result[0] >= distance) {
result[0] = distance;
result[1] = list[i];
result[2] = list[j];
}
}
}
return result;
}
此时,时间复杂度为:T(n) = O(n2)
分治法求解
求解步骤
划分子问题
将问题化为相等的两份
先将子问题按照横坐标的大小进行排序,然后求出中间值,将其分为两部分
求解子问题
递归的对两个子问题进行求解
①先求出左半部分的最短距离(第一对点)
②再求出右半部分的最短距离(第二对点)
③求得中间部分的最短距离(第三对点)
合并问题的解
比较三个值中最小的一个,将其返回
重点问题(第三对点的求解)
先求得第一对点和第二对点,得出两者最小值d,这时S1和S2中任意点对之间的距离小于或等于d,但S1、S2交界的垂直带形区(由所有与中轴线的x坐标值相差不超过d的点构成)中的点对之间的距离有可能小于d
若在PL中存在一个点s
,则PR 中需要计算距离点的y轴坐标一定位于区间[s.y-d, s.y+d]之间,即这样的点一定落在一个d×2d的矩形区域内
根据鸽舍原理:将该区域分割为d/2 * 2d/3的六块大小相同的区域
设两个点同时存在于一块小区域内,此时最远点距为√((d/2)2+(2d/3)2) =√ 25d2/36 < d2
这与d的定义(S1和S2中任意点对之间的距离小于或等于d)不符,即点s
所需要计算点最多有6个
即该步骤可在线性时间内完成
实现代码
提供给外界的方法:
/**
* 使用分治法求解最近点对问题
* @param list 所有点数组
* @return 最短距离
*/
public static Object[] seekDistance(Point[] list) {
//将数组中点按x进行排序
quiSort(list, 0, list.length-1, "x");
//递归求解
return ClosestDistance(list,0,list.length-1);
}
主要计算方法:
/**
* 使用二分法进行递归求出最短距离
* @param xArray x轴排列
* @param yArray y轴排列
* @param begin x开始索引
* @param end x结束索引
* @return 最短距离
*/
private static Object[] ClosestDistance(Point[] xArray, int begin, int end) {
Object[] result = null;
//若此时点数少于四个,则使用蛮力法求解
if(end - begin + 1 < 4) {
return simDistance(xArray, begin, end);
}
//求出中间值
int midIndex = (begin+end)/2;
//使用二分法对左半部分求解
Object[] minLeft = ClosestDistance(xArray, begin, midIndex);
//对右半部分求解
Object[] minRight = ClosestDistance(xArray, midIndex+1, end);
//求出两边所求出的距离最小值
if((double)minLeft[0] < (double)minRight[0]) {
result = minLeft;
}else {
result = minRight;
}
//取出最短距离
double minDistance = (double)result[0];
//根据最小值数组中划分出垂直带形区
//建立两个个数组将该区域内的数据进行收纳
//而根据鸽舍原理得出每一个点所需要计算的最多有6个
ArrayList<Point> lList = new ArrayList<Point>();
ArrayList<Point> rList = new ArrayList<Point>();
int index = begin;
while(xArray[index].x <= xArray[midIndex].x - minDistance) {
index++;
}
while(xArray[index].x <= xArray[midIndex].x) {
//处在左半部分的点
lList.add(xArray[index]);
index++;
}
while(xArray[index].x <= xArray[midIndex].x + minDistance && index <= end) {
//处在右半部分的点
rList.add(xArray[index]);
index++;
}
//转化为数组
Point[] lArray = new Point[lList.size()];
Point[] rArray = new Point[rList.size()];
lList.toArray(lArray);
rList.toArray(rArray);
//按照y进行排序
quiSort(lArray, 0, lArray.length-1, "y");
quiSort(rArray, 0, rArray.length-1, "y");
//计算第三对点距离
double minMid = minDistance ;
for(int i=0; i< lArray.length; i++){
for(int j=0; j< rArray.length; j++) {
//判断,由于此时y是有序的,可根据y的差值判断是否需要计算
//当y值太小时,直接进行下一次循环
if((lArray[i].y - rArray[j].y) < 0 - minDistance) {
continue;
}
//两个点距离大于最小值时,直接跳出此层循环
if((lArray[i].y - rArray[j].y) > minDistance) {
break;
}
//选出最小值
minMid = Point.distance(lArray[i], rArray[j]);
if(minMid < minDistance) {
minDistance = minMid;
//存储距离及点对
result[0] = minDistance;
result[1] = lArray[i];
result[2] = rArray[j];
}
}
}
return result;
}
快排方法:
/**
* 快速排序
* @param list 需要排序的行列
* @param i 数据开始索引
* @param j 数据结束索引
* @param type 需要排序的坐标类型
* @return 排序的结果
*/
private static Point[] quiSort(Point[] list, int i, int j, String type) {
//长度大于一时,使用递归进行排序(相当于一个二叉树)
if(i < j) {
int mid = onceQuiSort(list, i, j, type);
//左边排序,需使得mid-1,不然会死循环
quiSort(list, i, mid-1, type);
//右边排序
quiSort(list, mid+1, j, type);
}
return list;
}
/**
* 一次快速排序
* @param list 需要排序的行列
* @param i 头指针
* @param j 尾指针
* @param type 需要排序的坐标类型
* @return 排序结束中间指针
*/
private static int onceQuiSort(Point[] list, int i, int j, String type) {
//取出第一位元素作为界点
if(type.equals("x")) {
Point point = list[i];
while(i != j) {
while(i != j && list[j].x >= point.x) {
j--;
}
list[i] = list[j];
while(i != j && list[i].x <= point.x) {
i++;
}
list[j] = list[i];
}
list[i] = point;
}else {
Point point = list[i];
while(i != j) {
while(i != j && list[j].y >= point.y) {
j--;
}
list[i] = list[j];
while(i != j && list[i].y <= point.y) {
i++;
}
list[j] = list[i];
}
list[i] = point;
}
return i;
}
测试方法:
Point[] list = new Point[10];
list[0] = new Point(4,10);
list[1] = new Point(3,7);
list[2] = new Point(9,7);
list[3] = new Point(3,4);
list[4] = new Point(5,6);
list[5] = new Point(5,4);
list[6] = new Point(6,3);
list[7] = new Point(8,1);
list[8] = new Point(3,0);
list[9] = new Point(1,6);
Object[] result = seekDistance(list);
System.out.println("最短距离为:"+(double)result[0]);
System.out.println("所对应的点对为:"+(Point)result[1]+"、"+(Point)result[2]);
测试结果
时间复杂度
递归公式为:
T(n) = O(1) n<4
T(n) = 2T(n/2) + O(n) n>=4
解得时间复杂度为:T(n) = nlog2n