分治法求最近点对问题
这个问题熬了我一个晚上改错,都是空指针错误,从网上看了好多资料都没什么用,但就是那一瞬间看到了代码的某一行发现了错误,在数据与数组中某个值比较时,数组范围弄错了,导致比较过程中会指向数组外的地方(还是空指针问题)。现写下分析过程,以便后续查验
问题描述
补充要求:随机生成横纵坐标值均在1-10的30个点(浮点数),递归求出距离最近的一对点的坐标及其该两点距离值并以显示。
时间复杂度
如果是用蛮力法,那么时间复杂度毫无疑问是O(n^2),并不是最优解。现采用分治法递归函数,使之时间复杂度为O(nlogn)。
基本思想
30个点为0到29,数集为S。
基本思想:将S分成左右不相交的两个子集,求每个子集的最近点对,以及分别位于两个子集中的
所有点对的最近点对(由左边一个点,右边一个点构成),最后通过比较得到S中的最近点对。
做法
(1)当n≤2时直接求解。n为1时则返回一个double型的最大值,n为2时则记录两点并返回两点间距离。n>3时按如下步骤。
(2)分解:根据x坐标,对S中的点升序排序,以经过第n/2个点做中垂线,分为左右两侧,将S均分成两个子集:S[1…m] 和[m+1…n] ,其中 (int) m=n/2 。这样,一部分在中垂线的左边或在中垂线上,另一部分在中垂线右边或在中垂线上。
(3)递归:用同样的分治算法分别求中垂线两边的最近点对的距离min1和min2。
(4)组合:比较min1和min2,取较小值赋给mint,求中垂线左边一个点和右边一个点的最近距离看其是否小于mint,若小于则改变点对,更新mint。
难点:求中垂线左边一个点和右边一个点的最近距离
如果还是左边和右边一个一个算的话,又回到了O(n^2)。事实上不用遍历每一个点,有些点没有必要去算。先给出具体方法:我们现已比较出左侧和右侧两方的最短距离mint,那么以中垂线为界两边扩展mint的距离,可能符合要求的点均在这个区域内。这些点可以比较其与中垂线的距离来筛选,x的差距绝对值小于mint的进入新的数组K[n]。如图:
那么K中数据怎么比较呢,现介绍一个方式。
取一个mint×2*mint方框,若其中不存在小于mint的点对,那么这个方框中最多存在六个点。
所以我们往K里放数据时应是按y的值从小到大排好的,这样我们遍历K的时候只需往后多找六个点就行
了。这样使得此过程时间复杂度为O(n)。但是需提前将30个点分别按x和y排好顺序,存到两个数组中,以便后续使用。
上代码(如有人查看,请不要直接搬抄代码,无意义)
import java.util.Arrays;
import java.util.Comparator;
public class MinDistance {
static Points point = new Points();
public static void main(String[] args) {
node[] a = new node[30];
createPoint(a);
node[] b = a.clone();
Arrays.sort( a , new cmp1() ); //按x排序
Arrays.sort( b , new cmp2() ); //按y排序
double dis = minDis(0,29,a,b,point);
System.out.println("最近点对:");
System.out.println("( "+point.x1+" , "+point.y1+" ),"+"( "+point.x2+" , "+point.y2+" )");
System.out.println("最近距离:"+dis);
}
public static void createPoint(node[] a) { //创建随机点
for(int i=0;i<30;i++)
{
a[i] = new node();
a[i].x = 10*Math.random();
a[i].y = 10*Math.random();
for(int j=0;j<i;j++) {
if( a[j].x==a[i].x && a[j].y==a[i].y ) {
i--;
break;
}
}
}
}
public static double distance(int i,int j,node[] a) { //计算距离
double n = Math.pow(a[i].y-a[j].y, 2);
double m = Math.pow(a[i].x-a[j].x, 2);
double w = Math.sqrt(m+n);
return w;
}
public static double minDis(int low,int high,node[] a,node[] b,Points p) {
if(high-low==0)
return Double.MAX_VALUE;
else if(high-low==1) {
p.x1 = a[low].x;
p.x2 = a[high].x;
p.y1 = a[low].y;
p.y2 = a[high].y;
return distance(low,high,a);
}
else {
int m = (low+high)/2;
Points c = new Points();
Points d = new Points();
double min1 = minDis(low,m,a,b,c);
double min2 = minDis(m+1,high,a,b,d);
double mint;
if(min1 < min2){
p.x1 = c.x1;
p.x2 = c.x2;
p.y1 = c.y1;
p.y2 = c.y2;
mint = min1;
}else {
p.x1 = d.x1;
p.x2 = d.x2;
p.y1 = d.y1;
p.y2 = d.y2;
mint = min2;
}
double x = a[m].x;
int k = 0;
node[] K = new node[30];
for(int i=0;i<30;i++) {
if(mint>=(Math.abs(b[i].x-x))) {
K[k] = new node();
K[k].x = b[i].x;
K[k].y = b[i].y;
k++;
}
}
for(int i=0;i<(k-1);i++) {
int temp = (i+7)<k?(i+7):k;
for(int j=i+1;j<temp;j++) { //记住此地方,空指针,为了它真是用了好长时间,长记性
if(distance(i,j,K)<mint) {
mint = distance(i,j,K);
p.x1 = K[i].x;
p.x2 = K[j].x;
p.y1 = K[i].y;
p.y2 = K[j].y;
}
}
}
return mint;
}
}
}
class Points{ // 存储最近点对
public double x1;
public double y1;
public double x2;
public double y2;
}
class node{ //存储30个点
public double x=0;
public double y=0;
}
class cmp1 implements Comparator<node>{
public int compare(node a,node b){
if( a.x < b.x)
return -1;
else if(a.x >b.x)
return 1;
else return 0;
}
}
class cmp2 implements Comparator<node>{
public int compare(node a,node b){
if( a.y < b.y)
return -1;
else if(a.y >b.y)
return 1;
else return 0;
}
}
个人总结,如有帮助,感谢点赞,如有问题,下方评论