最近对
思路
最近点对问题要求在一个包含n个点的集合里,找出距离最近的两个点。
最近点对问题的一个最重要的应用的统计学中的聚类分析。对于n个数据点的集合,层次聚类分析希望基于某种相似度度量标准将数据点构成的簇按照层次关系组织起来。对于数值型数据,相似度度量标准通常采用欧几里得距离;对于文本和其他非数值型数据,通常采用诸如汉明距离这样的相似度度量标准。自上而下的算法初始时一般把每个元素作为一个分离的簇,然后合并最临近的簇,使其成为更大的后继簇。
目前演示的话,将使用欧几里得距离进行判断标准
d
(
p
i
.
p
j
)
=
(
x
i
−
x
j
)
2
+
(
y
i
−
y
j
)
2
d(p_i.p_j)=\sqrt{(x_i-x_j)^2+(y_i-y_j)^2}
d(pi.pj)=(xi−xj)2+(yi−yj)2
显然,求解该问题的蛮力算法应为:分别计算每一对点之间的距离,然后找出距离最小的那一对。当然,为了避免对同一对计算两次距离,我们只考虑i<j的点对
伪代码
BruteForceClosestPoints(p)
//使用蛮力算法求平面中距离最近的两点
//输入:一个二维数组
//输出:两个最近点的距离
d <- 无穷
for i <- 1 to n-1 do
for j <- i+1 to n do
d <-min(d,sqrt((xi-xj)^2+(yi-yj)^2)
return d
显然,平方根小的,被开方数也小,所以,我们其实可以避免求平方根
因此,算法的基本操作就是求平方。平方操作的执行次数C(n)
∈
Θ
(
n
2
)
\in \Theta(n^2)
∈Θ(n2)
Java代码实现
public class BruteForceClosestPoints {
public static void main(String[] args) {
int[][] matrix = {{1,2},{2,2},{3,3},{4,4},{5,5}};
double d = ClosestPoints(matrix);
System.out.println("最近距离:"+d);
}
public static double ClosestPoints(int[][] matrix){
int n = matrix.length;
double d = Double.MAX_VALUE;
for (int i=0;i<n;i++){
int x1 = matrix[i][0];
int y1 = matrix[i][1];
for (int j=i+1;j<n;j++){
int x2 = matrix[j][0];
int y2 = matrix[j][1];
System.out.println(Math.pow(x1-x2,2)+Math.pow(y1-y2,2));
d = Math.min(d,Math.pow(x1-x2,2)+Math.pow(y1-y2,2));
}
}
return d;
}
}
结果如下:(注意,这是没开方的数据,即距离的平方)
分治法优化后的代码
凸包问题
概念
在平面或者高维空间的一个给定点集合中寻找凸包,被视为计算几何中最重要的问题之一,甚至有人认为这是最重要的问题。该问题之所以如此突出,是由于许多各种各样的应用要么本身就是凸包问题,要么其中一部分需要按照凸包问题来解决。大多数此类应用都是基于这样一个原理:凸包能方便地提供目标形状或给定数据集的一个近似。例如,在计算机动画中,用物体的凸包替换物体本身,能够加快碰撞检测的速度…
先来定义什么是凸集合:
定义——对于平面上的一个点集合(有限的或无限的),如果以集合中任意两点p和q为端点的线段都属于该集合,我们说这个集合是凸的。
现在来讲解凸包的概念。直观地说,对于平面上n个点的集合,它的凸包就是包含所有这些点(或者在内部,或者在边界上)的最小凸多边形。下面这个凸包的定义可以应用于任意集合,包括那些正好位于一条直线上的点的集合。
定义——一个点集合S的凸包是包含S的最小凸集合(‘最小’意指,S的凸包一定是所有包含S的凸集合的子集)
如果S是凸的,它的凸包显然是它本身。如果S是两个点组成的集合,它的凸包是连接这两个点的线段。如果S是由三个不同线的点组成的集合。它的凸包是以这三个点为顶点的三角形,如果三点同线,凸包是以距离最远的两个点为端点的线段,对于更大集合的凸包的例子,请参见图3.6。
定理:任意包含n>2个点(不共线的点)的集合S的凸包是以S中的某些点为顶点的凸多边形(如果所有的点都位于一条直线上,多边形退化成一条线段,但它的两个端点仍然包含在S中)
直到现在,大家对凸包应该有了一点理解了,我就直接开门见山了,凸包问题就是为一个有n个点的集合构造凸包的问题
使用蛮力法解决凸包问题
对于一个n个点集合中的两个点
p
i
和
p
j
p_i和p_j
pi和pj,当且仅当该集合中的其他点都位于穿过这两点的直线的同一边时,它们的连线是该集合凸包边界的一部分。对每一对点都做一遍检验之后,满足条件的线段构成了该凸包的边界。
为了实现这个算法,需要使用一些解析几何的基本知识。首先,在坐标平面上穿过两个点
(
x
i
,
y
1
)
,
(
x
2
,
y
2
)
(x_i,y_1),(x_2,y_2)
(xi,y1),(x2,y2)的直线是由下列方程定义的:
a
x
+
b
y
=
c
ax+by=c
ax+by=c
其中,
a
=
y
2
−
y
1
,
b
=
x
1
−
x
2
,
c
=
x
1
y
2
−
y
1
x
2
a=y_2-y_1,b=x_1-x_2,c=x_1y_2-y_1x_2
a=y2−y1,b=x1−x2,c=x1y2−y1x2
其次,这样一根直线把平面分成两个半平面:其中一个半平面中的点都满足
a
x
+
b
y
>
c
ax+by>c
ax+by>c,而另一个半平面中的点都满足ax+by<c(当然,对于线上的点来说,ax+by=c)。因此,为了检验某些点是否位于这条直线的同一边,只需把每个点带入ax+by-c,检验这个表达式的符号是否相同。
伪代码
ConvexHullAlgorithm(Matrix)
//输入点集-一个二维数组
//输出凸包
for i <- 1 to n-1 do
for j <- i+1 to n do
a = y2-y1
b = x1-x2
c = x1y2-y1x2
int flat = 0;
for k <- 1 to n-1 do
if flat=0&&ax+by-c>0 flat=1;
else if flat=0&&ax+by-c<0 flat=-1;
if flat*(ax+by-c)<0 break
if k==n target.append(x1,y1,x2,y2)
return target
该算法的时间效率属于 O ( n 3 ) O(n^3) O(n3):对于不同点的每一个n(n-1)/2来说,我们要对其他n-2个点求出ax+by-c的符号。
Java代码实现
package com.算法.蛮力法;
import java.util.*;
/**
* @Author Lanh
**/
public class 凸包问题 {
public static void main(String[] args) {
int[][] matrix = {{12,5},{23,1},{3,27},{65,4},{2,5},{5,3},{3,7},{5,7}};
Set<Point> target = ConvexHull(matrix);
System.out.println(target);
}
public static Set<Point> ConvexHull(int[][] matrix){
int n = matrix.length;
Set<Point> target = new HashSet<>();
for (int i=0;i<n;i++) {
int x1 = matrix[i][0];
int y1 = matrix[i][1];
for (int j = i + 1; j < n; j++) {
int x2 = matrix[j][0];
int y2 = matrix[j][1];
int a = y2 - y1;
int b = x1 - x2;
int c = x1 * y2 - y1 * x2;
int k = 0;
int flat = 0;
while (k < n) {
int x = matrix[k][0];
int y = matrix[k][1];
int value = a * x + b * y - c;
if (flat == 0 && value > 0) flat = 1;
else if (flat == 0 && value < 0) flat = -1;
if (flat * value < 0) break;
k++;
}
if (k==n) {
target.add(new Point(x1,y1));
target.add(new Point(x2,y2));
}
}
}
return target;
}
static class Point {
int x;
int y;
Point(int x,int y){
this.x = x;
this.y = y;
}
@Override
public String toString() {
return "Point{" +
"x=" + x +
", y=" + y +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Point point = (Point) o;
return x == point.x && y == point.y;
}
@Override
public int hashCode() {
return Objects.hash(x, y);
}
}
}