为了方便接下来更好的组织数据,构建如下数据结构
#define eps 1e-8 //方便后面精度的计算
struct point{
double x,y;
}
struct segment{
point seg[2];
}
如果一条线段的端点是有次序之分的,我们把这种线段成为有向线段(directed segment)。如果有向线段p1p2的起点p1在坐标原点,我们可以把它称为矢量(vector)p2。
设二维矢量P(x1,y1),Q(x2,y2),则矢量加分定义为:P+Q=(x1+x2,y1+y2),同样有矢量减法就不赘述了。并且有满足交换律,和结合律,分配律。
距离公式:
假设两点P(x1,y1),Q(x2,y2),其距离为sqrt((x1-x2)(x1-x2)+(y1-y2)(y1-y2)
double dist(point p,point q){
return sqrt((x1-x2)*(x1-x2)-(y1-y2)*(y1-y2));
}
计算叉积的作用有非常多,是直线和线段的核心算法。设P(x1,y1),Q(x2,y2),则其叉积为P×Q=x1y2-x2y1。
double cross(point a,point b){
return a.x*b.y-a.y*b.x;
}
叉积具有一个重要性质是可以通过它的符号判断两个矢量相互之间的顺逆时针关系:
若P×Q>0 则P在Q的顺时针方向
若P×Q<0 则P在Q的逆时针方向
若P×Q=0 则P与Q共线,但有可能为同向或反向
矢量叉积可以看作原点,P点,Q点三点,因此可以推广成任意的3个点来判断它们之间的关系。
假设P(x1,y1),Q(x2,y2),R(x3,y3)三点,
将P和Q连接,对P,Q,R三点进行叉积即:
K=x1y2-x2y1+x2y3-x3y2+x3y1-x1y3
若K>0,其意义为在P点,面向Q点,R在PQ线段的左边
若K<0,其意义为在P点,面向Q点,R在PQ线段的右边
若K=0,其意义为在P点,面向Q点,R在PQ线段上
int mul(point a,point b,point c){
double k=x1*y2-x2*y1+x2*y3-x3*y2+x3*y1-x1*y3;
if(k>eps)return 1;
else if(k<-eps)return -1;
else return 0;
}
这个知识点在后面有奇用。
只要判断该点的横坐标和纵坐标是否夹在矩形的左右边和上下边之间。
但如果点不规则就不好判断,那么我们又可以用到叉积:判断一个点是否在两条线段之间夹着就转化成,判断一个点是否在某条线段的一边上,就可以利用叉乘的方向性,来判断夹角是否超过了180度 此图来自[https://www.cnblogs.com/fangsmile/p/9306510.html]
设E为任意点,ABCD为矩形
那么我们只要判断(AB×AE)(CD×CE)>=0并且(DA×DE)(BC×BE)>=0。
struct Rec{
point a,b,c,d;
}
bool pointisinRectangle(point e,Rec r){
if(mul(r.a,r.b,r.e)*mul(r.c,r.d,e)>=0&&mul(r.d,r.a,e)*mul(r.b,r.c,e)>=0)return true;
else return false;
}
只要判断两个矩形的左右边界以及上下边界即可
充要条件是:圆心在矩形中并且圆的半径小于等于圆心到矩形距离的最小值。
那么点到各线段的距离为:
struct Rec{
point a[4];
}
double dist(point R,point a,point b){
double A=(a.y-b.y)/(a.x-b.x),B=1,C=a.y-(a.y-b.y)/(a.x-b.x);
double d=fabs((A*R.x+B*R.y+C)/sqrt(A*A+B*B);
}
//其中r为圆的半径,R为圆心点,j为矩形
bool isinRectangle(double r,point R,Rec j){
if(!pointisinRectangle(R,j))return false;
double min=1e18;
for(int i=0;i<4;i++){
//遍历各个点求各线段到圆心的距离
min=min>dist(R,j.a[i],j.a[(i+1)%4]);
}
if(r<min)return true;
else return false;
}
假设有四个点P(x1,y1),Q(x2,y2),S(x3,y3),R(x4,y4),要判断PQ与SR是否相交,一般来说有两种可行的方法。
1.快速排斥法:PQ,SR分别作为对角线做 R1,R2两个矩形,如果两个矩形相交那么两线段即相交。
2.叉积判断法:如果两个线段相交,那么S,R点必须分别在PQ线段的两侧或者在PQ上,并且P,Q点也必须满足在SR线段的两侧或者在SR上,那么我们就可以运用上面的叉积知识了。
bool isintersectant(segment a,segment b){
if(mul(a.seg[0],a.seg[1],b.seg[0])*mul(a.seg[0],a.seg[1],b.seg[1]<=0&&mul(b.seg[0],b.seg[1],a.seg[0])*(b.seg[0],b.seg[1],a.seg[1])<=0)
return true;
else return false;
}
众所周知,对于一个三角形的三个顶点,它们的叉积的绝对值/2即为三角形的面积,那么也可以推广到n边形的面积:
double square(int n,point p[]){
double k=0;
for(int i=0;i<n;i++){
k+=p[i].x*p[(i+1)%n].y-p[i].y*p[(i+1)%n].x;
}
return k;
}
点集Q的凸包(convex hull)是指一个最小凸多边形,满足Q中的点或者在多边形边上或者在其内。下图中由红色线段表示的多边形就是点集Q={p0,p1,…p12}的凸包
凸包的求法:
凸包算法的时间复杂度O(n*logn)
其算法过程如下:
先寻找y坐标最小的点,如果有y坐标相同的,则取x偏小的点作为起始点。
设
压p0进栈S
压p1进栈S
压p2进栈S
for i ← 3 to m
do while 由S的栈顶元素的下一个元素、S的栈顶元素以及pi构成的折线段不拐向左侧
对S弹栈
压pi进栈S
return S;
伪代码可能对于一些刚入门的朋友不大友好,附上源代码:
#include
#include
#include
#define eps 1e-8
using namespace std;
struct point{
double x,y;
};
point p[10005];
int ord[10005];
double multi(point p1,point p2,point p0){
//求叉积
double x1=p1.x-p0.x;
double y1=p1.y-p0.y;
double x2=p2.x-p0.x;
double y2=p2.y-p0.y;
return(x1*y2-x2*y1);
}
double dist(point a,point b){
//求距离
return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
}
int mul(point a,point b,point c){
//这个不用多说了把 前面用了不知道多是次了
double k=a.x*b.y-a.y*b.x+b.x*c.y-b.y*c.x+c.x*a.y-c.y*a.x;
if(k>eps)return 1;else
if(k<-eps)return -1;else return 0;
}
bool cmp(int a,int b){
//为了避免结构体内部大规模的移动,将各个索引进行移动,用空间换时间
int i=mul(p[ord[0]],p[a],p[b]);
if(i==0)return (dist(p[ord[0]],p[a])<dist(p[ord[0]],p[b]));
else return i>0;
}
int main(){
int n,f,j=2;
double miny=1e18;
cin>>n;
for(int i=0;i<n;i++){
//输入并查找起始点
cin>>p[i].x>>p[i].y;
ord[i]=i;
if(p[i].y<miny){
miny=p[i].y;
f=i;
}else if(p[i].y==miny&&p[i].x<p[f].x){
f=i;
}
}
swap(ord[0],ord[f]); //将所找到的起始点索引放到数组最前端
if(n==2){
//当只有两个点的时候形成不了凸包,但可以直接输出
if(ord[0]==0)cout<<0<<" "<<1;
else cout<<1<<" "<<0;
}else{
sort(ord+1,ord+n,cmp); //将映射点根据以p0为中心的极角逆时针排序
int *stack=new int[10005];
int top=0;
stack[0]=ord[0];
for(int i=1;i<n;i++){
//这边伪代码都有讲到,可以对照看看
while(top>0&&mul(p[stack[top-1]],p[stack[top]],p[ord[i]])<=0)top--;
stack[++top]=ord[i];
}
for(int i=0;i<=top;i++){
cout<<stack[i]<<" ";
}
}
代码的作用为输出各个凸包最小多边形的顶点的索引。
突发奇想记录下最近学的几何知识,叉积的用法真是玄学,祝大家学业有成,万事如意!!!