1,(What)什么是凸包?
凸包(Convex Hull)是一个计算几何(图形学)中的概念。 在一个实数向量空间V中,对于给定集合X,所有包含X的凸集的交集S被称为X的凸包。X的凸包可以用X内所有点(X1,...Xn)的凸组合来构造. 在二维欧几里得空间中,凸包可想象为一条刚好包著所有点的橡皮圈。 用不严谨的话来讲,给定二维平面上的点集,凸包就是将最外层的点连接起来构成的凸多边形,它能包含点集中所有的点。(摘自百度百科)
2,(Why)为什么要用凸包?
在做一些范围匹配,范围包含,电子围栏项目的时候可以通过构造凸包来初步筛选,较少范围匹配次数,提升程序运行效率。
3,(How)Java代码的一种实现
凸包工具类:
package com.example.demo.convex;
import java.awt.geom.Path2D;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.stream.Collectors;
import static java.lang.Math.abs;
/**
* @author lxw
* @date 2020-08-17
*/
public class MyConvexHull {
private static int MAX_ANGLE = 4;
private double currentMinAngle = 0;
/**
* 最终生成的凸包的结果集
*/
private List<Point2D.Double> hullPointList;
private List<Integer> indexList;
private List<Point2D.Double> pointDoubles;
/**
* 凸包里面的起始点的在原pointDoubles里的下标
*/
private int firstIndex;
/**
* 获取一串点的凸包
*
* @param pointDoubleList
* @return
*/
public static List<Point2D.Double> getConvexPoint(List<Point2D.Double> pointDoubleList) {
// 去重处理,过滤重复点
pointDoubleList = pointDoubleList.stream().distinct().collect(Collectors.toList());
// 初始化参数值
MyConvexHull convexUtil = new MyConvexHull(pointDoubleList);
return convexUtil.calculateHull();
}
public MyConvexHull() {
}
/**
* 构造方法
*
* @param pointDoubleList
*/
public MyConvexHull(List<Point2D.Double> pointDoubleList) {
this.hullPointList = new LinkedList<>();
this.indexList = new LinkedList<>();
this.pointDoubles = pointDoubleList;
// 获取凸包的起始点的在pointDoubles里面的下标
firstIndex = getFirstPoint(pointDoubleList);
// 设置凸包的起始点
addToHull(firstIndex);
}
/**
* 计算返回凸包
*
* @return
*/
public List<Point2D.Double> calculateHull() {
for (int i = getNextIndex(firstIndex); i != firstIndex; i = getNextIndex(i)) {
addToHull(i);
}
return showHullPoints();
}
/**
* 把pointDoubles里面下标为index的点添加进凸包
*
* @param index
*/
private void addToHull(int index) {
indexList.add(index);
hullPointList.add(pointDoubles.get(index));
}
/**
* 展示凸包
*
* @return
*/
private List<Point2D.Double> showHullPoints() {
Iterator<Point2D.Double> itPoint = hullPointList.iterator();
List<Point2D.Double> resultList = new ArrayList<>();
while (itPoint.hasNext()) {
Point2D.Double p = itPoint.next();
resultList.add(new Point2D.Double(p.getX(), p.getY()));
}
return resultList;
}
/**
* 从下标currentIndex开始获取下一个在pointDoubles符合要求的凸包的点的下标
*
* @param currentIndex
* @return
*/
private int getNextIndex(int currentIndex) {
double minAngle = MAX_ANGLE;
double pseudoAngle;
int minIndex = 0;
for (int i = 0; i < pointDoubles.size(); i++) {
if (i != currentIndex) {
double dx = pointDoubles.get(i).getX() - pointDoubles.get(currentIndex).getX();
double dy = pointDoubles.get(i).getY() - pointDoubles.get(currentIndex).getY();
pseudoAngle = getPseudoAngle(dx, dy);
if (pseudoAngle >= currentMinAngle && pseudoAngle < minAngle) {
minAngle = pseudoAngle;
minIndex = i;
} else if (pseudoAngle == minAngle) {
if ((abs(pointDoubles.get(i).getX() - pointDoubles.get(currentIndex).getX()) >
abs(pointDoubles.get(minIndex).getX() - pointDoubles.get(currentIndex).getX()))
|| (abs(pointDoubles.get(i).getY() - pointDoubles.get(currentIndex).getY()) >
abs(pointDoubles.get(minIndex).getY() - pointDoubles.get(currentIndex).getY()))) {
minIndex = i;
}
}
}
}
currentMinAngle = minAngle;
return minIndex;
}
/**
* 获取最下(y坐标最小)的一个点,如果有多个最下的点,取其中最左(x坐标最小)的一个
*
* @param pointDoubleList
* @return
*/
private int getFirstPoint(List<Point2D.Double> pointDoubleList) {
int minIndex = 0;
for (int i = 1; i < pointDoubleList.size(); i++) {
if (pointDoubleList.get(i).getY() < pointDoubleList.get(minIndex).getY()) {
minIndex = i;
} else if ((pointDoubleList.get(i).getY() == pointDoubleList.get(minIndex).getY())
&& (pointDoubleList.get(i).getX() < pointDoubleList.get(minIndex).getX())) {
minIndex = i;
}
}
return minIndex;
}
/**
* 计算角度的偏离程度
*
* @param dx
* @param dy
* @return
*/
private double getPseudoAngle(double dx, double dy) {
if (dx > 0 && dy >= 0) {
// 平面直角坐标系的第一象限,返回值值域[0,1)
return dy / (dx + dy);
}
if (dx <= 0 && dy > 0) {
// 平面直角坐标系的第二象限,返回值值域[1,2)
return 1 + (abs(dx) / (abs(dx) + dy));
}
if (dx < 0 && dy <= 0) {
// 平面直角坐标系的第三象限,返回值值域[2,3)
return 2 + (dy / (dx + dy));
}
if (dx >= 0 && dy < 0) {
// 平面直角坐标系的第四象限,返回值值域[3,4)
return 3 + (dx / (dx + abs(dy)));
}
throw new Error("坐标有重复" + dx + dy);
}
/**
* 构造Path2D.Double对象,形成封闭多边形
*
* @param polygon
* @return
*/
public static Path2D.Double create(List<Point2D.Double> polygon) {
Path2D.Double generalPath = new Path2D.Double();
Point2D.Double first = polygon.get(0);
// 通过移动到指定坐标(以双精度指定),将一个点添加到路径中
generalPath.moveTo(first.getX(), first.getY());
for (int i = 1; i < polygon.size(); i++) {
// 通过绘制一条从当前坐标到新指定坐标(以双精度指定)的直线,将一个点添加到路径中。
generalPath.lineTo(polygon.get(i).getX(), polygon.get(i).getY());
}
// 将几何多边形封闭
generalPath.lineTo(first.getX(), first.getY());
generalPath.closePath();
return generalPath;
}
凸包测试类:
package com.example.demo.convex;
import java.awt.geom.Path2D;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.List;
/**
* @author lxw
* @date 2020-08-17
*/
public class ConvexHullTest {
public static void main(String[] args) {
Point2D point2D0 = new Point2D.Double(7.0, 5.0);
Point2D point2D1 = new Point2D.Double(3.0, 5.0);
// 构造原始多边形
List<Point2D.Double> pointDoubleList = buildPointDoubleList();
Path2D.Double polygon = MyConvexHull.create(pointDoubleList);
System.out.println("原始的凹多边形:");
System.out.println(polygon.contains(point2D0));
System.out.println(polygon.contains(point2D1));
// 构造凸包
List<Point2D.Double> convexHullList = MyConvexHull.getConvexPoint(pointDoubleList);
Path2D.Double convexHull = MyConvexHull.create(convexHullList);
System.out.println("原始的凹多边形对应的凸包:");
System.out.println(convexHull.contains(point2D0));
System.out.println(convexHull.contains(point2D1));
}
public static List<Point2D.Double> buildPointDoubleList() {
List<Point2D.Double> doubleList = new ArrayList<>();
Point2D.Double point2D0 = new Point2D.Double(0.0, 0.0);
Point2D.Double point2D1 = new Point2D.Double(10.0, 0.0);
Point2D.Double point2D2 = new Point2D.Double(5.0, 5.0);
Point2D.Double point2D3 = new Point2D.Double(10.0, 10.0);
Point2D.Double point2D4 = new Point2D.Double(0.0, 10.0);
doubleList.add(point2D0);
doubleList.add(point2D1);
doubleList.add(point2D2);
doubleList.add(point2D3);
doubleList.add(point2D4);
return doubleList;
}
}
4,几点思考
4.1 把实际问题抽象成理论问题,把理论问题具象到平面几何问题,再把平面几何问题抽象成为编程模型问题,然后解决,整个问题迎刃而解之。
比如把平面多边形抽象为编程里拥有一系列点的List,利用坐标的末减初来表达一个向量。
4.2 涉及到数学平面向量和矩阵的基本知识。
4.3 思考下各种算法情况下的时间复杂度问题。