一、【实验目的】
(1)熟悉分治算法的基本思想;
(2)理解分治算法改进的基本方法;
(3)理解不同情况下的算法时间复杂度的分析方法。
二、【实验内容】
平面上有n个点P1、P2、…Pi、…、Pn,n>1,Pi的坐标为(Xi,Yi)。请设计两种算法求距离最近的两个点及两者之间的距离,
并分析两种算法的时间复杂度。
要求算法测试时,点的个数不能少于10个。
提示:为保持与教材P34中算法参数的一致性,输入可采用3个数组,即:PN[]表示点的编号集合,PX[]表示对应X坐标,PY[]表示对应Y的坐标;
输出可以建立一个结构体,分别对应两个点的编号及对应的距离。以下是10个点的例子,其中点坐标都设为整数,可以用来做算法测试。
int PN[10]={0,1,2,3,4,5,6,7,8,9};
int PX[10]={-11,15,4,-5,2,3,-2,-7,-4,10};
int PY[10]={4,-3,6,-6,-1,2,7,5,-7,0};
另外,结构体可以采取如下定义:
typedef struct pS{
int iC1,iC2; //点的编号
float fD; //两点距离
} pT;
三、【实验源代码】
💖 ShortestDistance.java
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Scanner;
public class ShortestDistance
{
static int n;// 点的数量
static int[] px;// 横坐标
static int[] py;// 纵坐标
static Point[] ps;// 点集
static Point[] pointPair = new Point[2];// 存最小点对的下标
// 点类
static class Point
{
int id;
int x;
int y;
@Override
public String toString()
{
return "Point [id=" + id + ", x=" + x + ", y=" + y + "]";
}
public Point(int id, int x, int y)
{
super();
this.id = id;
this.x = x;
this.y = y;
}
}
public static void main(String[] args)
{
// 输入测试
// Scanner sc = new Scanner(System.in);
// n = sc.nextInt();// 点的个数
// px = new int[n];
// py = new int[n];
// for (int i = 0; i < n; i++)
// {
// px[i] = sc.nextInt();
// py[i] = sc.nextInt();
// }
n = 10;// 点的个数
ps = new Point[n];
px = new int[] { -11, 15, 4, -5, 2, 3, -2, -7, -4, 10 };
py = new int[] { 4, -3, 6, -6, -1, 2, 7, 5, -7, 0 };
for (int i = 0; i < n; i++)
ps[i] = new Point(i, px[i], py[i]);
double distance1 = bruteForce();
System.out.println("暴力法:" + pointPair[0] + " " + pointPair[1] + " " + distance1);
Arrays.sort(ps, (p1, p2) -> {
if (p1.x == p2.x)
return p1.y - p2.y;
return p1.x - p2.x;
});
double distance2 = partition(0, n - 1);
System.out.println("分治法:" + pointPair[0] + " " + pointPair[1] + " " + distance2);
}
// 方法2:分治(求闭区间 [l,r]的最小距离点对的距离,返回此距离)
static double ans = Double.MAX_VALUE;// 存全局最小距离
private static double partition(int l, int r)
{
if (l >= r)// 到头了
return Double.MAX_VALUE;
int mid = l + r >> 1;// m 是中点的下标
ans = Math.min(ans, partition(l, mid));// 分治求左区间的点对最小距离
ans = Math.min(ans, partition(mid + 1, r));// 分治求右区间的点对最小距离
int midX = ps[mid].x;// 中线的位置
// 向下取整,因为最小距离点对分布在左右两区间的情况下,只有可能在开区间(midX-curMini, midX+curMini)
// 注意,因为坐标是整数,所以浮点数部分不可能取到(舍去)
int lX = (int) (midX - ans);
int rX = (int) (midX + ans);
ArrayList<Point> midPoints = new ArrayList<>();// 存中间可能出现最小距离的最小点对
for (int i = l; i <= r; i++)
if (ps[i].x > lX && ps[i].x < rX)// 只添加有机会的点(减少计算量)
midPoints.add(ps[i]);
midPoints.sort((p1, p2) -> p1.y - p2.y);// 按纵坐标 y 排序
int size = midPoints.size();
// 开始求跨越两区间的点对的最小距离
for (int i = 0; i < size; i++)
{
Point a = midPoints.get(i);
for (int j = i + 1; j < size; j++)
{
Point b = midPoints.get(j);
// midPoints 按 y 排序,(a的y) 肯定<= (b的y)
if (b.y - a.y > ans)// 如果当前 by-ay 已经 > curMini,那么后面的 by 只会更大,剪枝break
break;
double d = calDistance(a, b);
if (d < ans)// 尝试更新答案
{
ans = d;
ans = ans;
pointPair[0] = a;
pointPair[1] = b;
}
}
}
return ans;
}
// 方法1:暴力
private static double bruteForce()
{
double res = Double.MAX_VALUE;
for (int i = 0; i < n; i++)
for (int j = i + 1; j < n; j++)
{
// 测试代码
// double t = calDistance(ps[i], ps[j]);
// if (t < res)
// {
// System.out.print(ps[i] + " ");
// System.out.print(ps[j] + " 距离:");
// System.out.println(t);
// res = Math.min(res, t);
// }
double d = calDistance(ps[i], ps[j]);
if (d < res)
{
pointPair[0] = ps[i];
pointPair[1] = ps[j];
res = d;
}
}
return res;
}
// 返回点 p1 和 点p2 的欧几里得距离
private static double calDistance(Point p1, Point p2)
{
return Math.sqrt(Math.pow(Math.abs(p1.x - p2.x), 2) + Math.pow(Math.abs(p1.y - p2.y), 2));
}
}
四、【实验结果】
暴力法:Point [id=3, x=-5, y=-6] Point [id=8, x=-4, y=-7] 1.4142135623730951
分治法:Point [id=8, x=-4, y=-7] Point [id=3, x=-5, y=-6] 1.4142135623730951