内容三 最近点对
对平面上给定的N个点,给出所有点对的最短距离
即,输入是平面上的N个点,输出是N点中具有最短距离的两点
-
要求随机生成N个点的平面坐标,应用穷举法编程计算出所有点对的最短距离
-
要求随机生成N个点的平面坐标,应用分治法编程计算出所有点对的最短距离
穷举法:两层循环,枚举出平面上所有点对的距离,得到最小的,时间复杂度 ,辅助空间
分治法:按x轴排序,先大致分成两半找,假设中心点是A()假设两边找到的最小距离是d,那么中间交叉的部分在距离、与 这两条垂线中间找(此时按y轴排序用穷举法)。至于为什么,详见www.360doc.com/content/19/…
触底:所划分的区域内有不大于3个点。
import java.util.*;
/**
* @author SJ
* @date 2020/10/14
*/
public class FindShortestDistance {
static class Point {
private Integer x;
private Integer y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
//计算两点之间距离
public int getDistance(Point a){
return (int) (Math.pow((this.x-a.x),2)+Math.pow((this.y-a.y),2));
}
@Override
public String toString() {
return "(" + x +
", " + y +
')';
}
}
//生成num个随机点放在list内
public static List<Point> generateRandomPoint(int num){
List<Point> list=new ArrayList<>();
int[] xs=RandomUtil.randomBetween100(num);
int[] ys=RandomUtil.randomBetween100(num);
for (int i = 0; i < num; i++) {
list.add(new Point(xs[i], ys[i])) ;
}
return list;
}
//穷举法
public static double findShortestDistance(List<Point> points){
//将最短距离初始化为list中第0个点和第一个点之间的距离
int distance= points.get(0).getDistance(points.get(1));
for (int i = 0; i < points.size(); i++) {
for (int j = i+1; j <points.size() ; j++) {
//list内下标为i的点与下标为j的点之间的距离
int tempDistance=points.get(i).getDistance(points.get(j));
if (tempDistance<distance){
distance=tempDistance;
}
}
}
return Math.sqrt(distance);
}
//list排序
public static void sortByX(List<Point> points) {
Collections.sort(points, new Comparator<Point>() {
@Override
public int compare(Point o1, Point o2) {
return o1.x.compareTo(o2.x);
}
});
}
private static void sortByY(List<Point> points) {
Collections.sort(points, new Comparator<Point>() {
@Override
public int compare(Point o1, Point o2) {
return o1.y.compareTo(o2.y);
}
});
}
//分治法
public static double findShortestDistance2(List<Point> points,int low,int high){
//只剩两个点
if (high-low==1)
return Math.sqrt(points.get(high).getDistance(points.get(low)));
//还剩3个点
else if (high-low==2){
double a=Math.sqrt(points.get(low).getDistance(points.get(low+1)));
double b=Math.sqrt(points.get(low+1).getDistance(points.get(high)));
double c=Math.sqrt(points.get(low).getDistance(points.get(high)));
return Math.min(Math.min(a,b),c);
}
else {
int mid=(low+high)/2;
double leftMin=findShortestDistance2(points,low,mid);
double rightMin=findShortestDistance2(points,mid+1,high);
double min=Math.min(leftMin,rightMin);
//重点来了
//1.找到横坐标与中点横坐标距离在min以内的的点,不包括min
List<Point> candidate=new ArrayList<>();
for (int i = low; i <=high; i++) {
if (Math.abs(points.get(i).x-points.get(mid).x)<min)
candidate.add(points.get(i));
}
//找出这些点后根据纵坐标排序
sortByY(candidate);
//在这个小范围内用穷举法
for (int i = 0; i < candidate.size(); i++) {
for (int j = i+1; j <candidate.size(); j++) {
if ((candidate.get(j).y-candidate.get(i).y)>min)
break;
double temp=Math.sqrt(candidate.get(j).getDistance(candidate.get(i)));
if (temp<min)
min=temp;
}
}
return min;
}
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("输入点的数目:");
int num = scanner.nextInt();
//生成num个随机点存在pointList中
List<Point> pointList=generateRandomPoint(num);
System.out.println("生成的随机点对为:");
for (Point point : pointList) {
System.out.print(point+" ");
}
System.out.println();
//使用穷举法计算
double shortestDistance = findShortestDistance(pointList);
System.out.println("穷举所得最短距离为:"+shortestDistance);
//使用分治法计算
sortByX(pointList);
double shortestDistance2 = findShortestDistance2(pointList, 0, pointList.size() - 1);
System.out.println("分治法所得最短距离为:"+shortestDistance2);
}
}
测试结果:
"C:\Program Files\Java\jdk1.8.0_131\bin\java.exe"...
输入点的数目:
10
生成的随机点对为:
(12, 1) (3, 11) (12, 8) (10, 7) (14, 6) (3, 18) (1, 8) (9, 18) (15, 14) (5, 11)
穷举所得最短距离为:2.0
分治法所得最短距离为:2.0
Process finished with exit code 0
复杂度分析:分治法:取决于选择的排序算法,我用的是Java提供的Collections.sort(),其内部采取的是归并排序,时间复杂度是。递归,区间深度不超过.总得来说时间复杂度。
除了存对象用的List,辅助空间还有计算交叉部分的距离时用的list,下面这里。
内容四 子序列最大和
给出一个整数序列,选出其中连续且非空的一段使得这段和最大
要求:使用分治法解决
思路:
我们来分析一下这个问题, 我们先把数组平均分成左右两部分。
此时有三种情况:
- 最大子序列全部在数组左部分
- 最大子序列全部在数组右部分
- 最大子序列横跨左右数组
对于前两种情况,我们相当于将原问题转化为了规模更小的同样问题。
横跨左右数组的用枚举法:左边的选择从middle-1开始挨着盘得往左累加,把最累加最大得结果记下来;
右边的从middle+1开始也是同样的方法。最后三个部分加起来就是第三中情况的结果。
随机数还是调用那个类,范围从-25到+25
public static int[] randomBetween100(int length){
int[] nums=new int[length];
for (int i = 0; i < nums.length; i++) {
nums[i]=(int)(Math.random()*(-50)+25);
}
return nums;
}
import java.util.Arrays;
import java.util.Scanner;
/**
* @author SJ
* @date 2020/10/14
*/
public class MaxSum {
public static int findMaxSum(int[] nums, int left, int right) {
//只剩一个数字
if (right == left)
return nums[left];
//只剩两个值
else if (right - left == 1) {
return Math.max(Math.max(nums[left], nums[right]), (nums[right] + nums[left]));
}
int mid = (left + right) / 2;
//左边序列最大和
int leftMax = findMaxSum(nums, left, mid - 1);
//右边序列最大和
int rightMax = findMaxSum(nums, mid + 1, right);
//找交叉项
int rightSum = 0;
int tempRightMax = nums[mid + 1];
for (int i = mid + 1; i <= right; i++) {
rightSum += nums[i];
if (rightSum > tempRightMax)
tempRightMax = rightSum;
}
int leftSum = 0;
int tempLeftMax = nums[mid - 1];
for (int i = mid - 1; i >= left; i--) {
leftSum += nums[i];
if (leftSum > tempRightMax)
tempLeftMax = leftSum;
}
//跨界序列最大和
int middleMax = tempLeftMax + nums[mid] + tempRightMax;
return Math.max(Math.max(leftMax, rightMax), middleMax);
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
while (true) {
System.out.println("输入点序列长度:");
int length = scanner.nextInt();
int[] nums = RandomUtil.randomBetween100(length);
System.out.println("生成的随机序列为:" + Arrays.toString(nums));
int max = findMaxSum(nums, 0, nums.length - 1);
System.out.println("最大和为:" + max);
}
}
}
结果:
"C:\Program Files\Java\jdk1.8.0_131\bin\java.exe"...
输入点序列长度:
5
生成的随机序列为:[0, 15, -4, 19, 7]
最大和为:37
输入点序列长度:
6
生成的随机序列为:[12, 23, -7, 10, -12, -11]
最大和为:38
输入点序列长度:
7
生成的随机序列为:[2, 9, 19, 3, 23, 0, 8]
最大和为:53
输入点序列长度:
10
生成的随机序列为:[-18, 17, 24, 12, -4, 20, 4, -5, 19, -20]
最大和为:87
输入点序列长度:
时间复杂度为 , 空间复杂度为