凸包问题
下面有一系列的点,选择其中最少的点连接起来构成凸多边形并且包围所有的点假设不存在重合的点
通俗理解,假设下面每一个点都是钉在木板上的钉子,所求的多边形就是使用一根橡皮筋套在了所有钉子上的形状
凸多边形,红色圈起来的才是所求出来的点
步骤
- 找出所有点中y坐标最小的点(即最靠下的点)作为起始点
- 将其他点分别与起始点连接,按照逆时针方向分别给除起始点以外的每个点按照1,2,3...标号
- 流程
graph TD 1(开始)-->3(按顺序将起始点和其后一个点压入栈) 3-->4(按顺序再将记录一个点temp) 4-->5(栈顶的两个点和temp是否满足顶点选择) 5-->|满足|9(将temp入栈) 9-->7(temp是否是最后一个点) 7-->|否|4 7-->|是|8(结束) 5-->|不满足|6(删除不必要顶点) 6-->5
- 没有检查完所有点之前你不能说除起始点之外任意一个点是确定的,顶点选择只能排除错误的点
顶点选择
- 假设按照如上的方法得到了其中序号连续且从小到大的a,b,c三个点,如何判断这三个点是否满足凸多边形的特征(即判断a->b->c是一个逆时针的拐角)
- 如果是a->b->c是一条直线或者是一个顺时针的拐角,那么去除直中间的点连接其余两个点会更优,如下
判断a->b->c是一个逆时针的拐角
- 利用向量的叉积(\(\vec{bc}\times\vec{ba}\))来判断
- 根据向量叉积的右手法则
- 如果(\(\vec{bc}\times\vec{ba}\))大于0说明a->b->c是一个逆时针的拐角
- 如果(\(\vec{bc}\times\vec{ba}\))小于0说明a->b->c是一个顺时针的拐角
- 如果(\(\vec{bc}\times\vec{ba}\))等于0说明a->b->c是一条直线
- 根据叉积定义$\vec\times\vec$=(c.x-b.x)*(a.y-b.y)-(a.y-b.y)*(c.x-b.x)
Code by java
Point(点类)
public class Point implements Comparable<Point>{//实现Comparable可以使用Collections.sort()
public double x;//x坐标
public double y;//y坐标
static public double px,py;//p(初始点)的x,y坐标
static public void setp(double x,double y){//设置p点的静态方法
px=x;
py=y;
}
public Point(double x, double y) {//构造
this.x = x;
this.y = y;
}
public static int ccw(Point a,Point b,Point c){//判断顶点选择
double area=(c.x-b.x)*(a.y-b.y)-(a.x-b.x)*(c.y-b.y);//向量bc和向量ba的叉积
return area>0?1:area<0?-1:0;//叉积大于0返回1,小于0返回-1,等于0返回0
}
@Override
public int compareTo(Point i) {//假设不存在x,y坐标完全相同的点
double a=(x-px)/Math.sqrt((x-px)*(x-px)+(y-py)*(y-py));//向量px和x正半轴的夹角(x指该点)
double b=(i.x-px)/Math.sqrt((i.x-px)*(i.x-px)+(i.y-py)*(i.y-py));//向量pi和x正半轴的夹角
if(a==b){//如果在同一直线上就比较x坐标,x坐标大的较大
a=x;
b= i.x;
}
return a>b?1:-1;
}
@Override
public String toString() {//重载toString
return "Point{" +
"x=" + x +
", y=" + y +
'}';
}
}
Main
public class Main {
public static void main(String args[]) throws IOException {
ArrayList<Point> cp=new ArrayList<>();//创建ArrayList对象,泛型为Point
BufferedReader bf=new BufferedReader(new FileReader("point.txt"));//从文件中读取点
String s;
while ((s=bf.readLine())!=null){
String[] a=s.split(" ",2);
cp.add(new Point(Double.valueOf(a[0]),Double.valueOf(a[1])));//将每个点添加到ArrayList中
}
double x=cp.get(0).x,miny=cp.get(0).y;
for(Point i:cp){//选出y坐标最小的点p
if(i.y<miny){
x=i.x;
miny=i.y;
}
}
for(int i=0;i<cp.size();i++)//删除基础点p
if(cp.get(i).x==x&&cp.get(i).y==miny)
cp.remove(i);
Point.setp(x,miny);//在点类中设置p点
System.out.println("P点:{x="+x+",y="+miny+"}");
Collections.sort(cp);//已p点为基础逆时针排序
Collections.reverse(cp);//逆序
System.out.println("排序后:");
for(Point i:cp)
System.out.println(i);
Stack<Point> sp=new Stack<>();//创建以Point为泛型的栈
sp.push(new Point(Point.px,Point.py));//将p点压入栈
sp.push(cp.get(0));//将排好序的第一个点压入栈
for(int i=1;i<cp.size();i++){//从排好序的第二个点开始
Point top =sp.pop();//抛出元素并保存
int a=Point.ccw(sp.peek(), top,cp.get(i));//a(sp.peek()),b(top),c(cp.get(i))三个点,是否满足顶点选择
while (sp.size()>0&&a<1){//栈中还有点并且a,b,b不满足顶点选择
//abc构成顺时针拐角或者直线位置错误都出现在top点
top=sp.pop();//抛出错误的b(top)点
a=Point.ccw(sp.peek(), top,cp.get(i));//a(sp.peek()),b(top),c(cp.get(i))三个点,是否满足顶点选择
}
sp.push(top);//将b(top)入栈
sp.push(cp.get(i));//将c(cp.get(i))入栈
}
System.out.println("所选顶点:");
while(sp.size()!=0){
System.out.println(sp.pop());
}
}
}
结果:
P点:{x=1.0,y=0.0}
排序后:
Point{x=2.0, y=0.0}
Point{x=3.0, y=1.0}
Point{x=3.0, y=2.0}
Point{x=2.0, y=1.0}
Point{x=3.0, y=3.0}
Point{x=2.0, y=2.0}
Point{x=1.0, y=1.0}
Point{x=1.0, y=2.0}
Point{x=0.0, y=3.0}
Point{x=0.0, y=2.0}
Point{x=0.0, y=1.0}
所选顶点:
Point{x=0.0, y=1.0}
Point{x=0.0, y=3.0}
Point{x=3.0, y=3.0}
Point{x=3.0, y=1.0}
Point{x=2.0, y=0.0}
Point{x=1.0, y=0.0}
point.txt
1 0
2 0
3 1
2 1
1 1
0 1
0 2
1 2
2 2
3 2
3 3
0 3