计算几何基础入门详解

计算几何基础

1、最开始要提一下的还是精度问题,这在计算几何中可以说非常关键!!!

const double eps=1e-8; //我一般都用eps表示,控制在1e-8左右,当然有些题会有它的要求
const double PI=acos(-1.0); //π
int sgn(double x){ //判断一个数是否大于0
	if(fabs(x)<eps) return 0; //等于0
	if(x<0) return -1;	//小于0
	else return 1; //大于0
}

2、点的代码表示
用一个结构体来表示点,我们一般也把点积、叉积的重载操作符定义在点的结构体里,对之后写代码非常方便,而且有效,自己也可以自定义去重载一些运算符
对于点旋转某个角度后得到的坐标,本人不给予证明,感兴趣的同学请自行证明,或者百度 证明过程
关于点旋转这个问题让我想到我做过的一道最小椭圆覆盖的题,给出本人博客链接,感兴趣的同学可以去看一下
博客链接

struct Point{ 
	double x,y; //横纵坐标
	Point(){}
	Point(double _x,double _y) {x=_x;y=_y;}
	Point operator -(const Point &b)const {return Point(x-b.x,y-b.y);} //减 
	double operator ^(const Point &b)const {return x*b.y-y*b.x;} //叉积 
	double operator *(const Point &b)const {return x*b.x+y*b.y;} //点积 
	void transXY(double B){ //求点旋转B弧度后的坐标(90度为π/2弧度)
		double tx=x,ty=y;
		x=tx*cos(B)-ty*sin(B); 
		y=tx*sin(B)+ty*cos(B);
	}
};

3、向量(既然讲到了点就顺便提一下)
向量是什么我就不解释了,大家中学时期都已经学过了,我只讲在程序中怎么去表示它
定义:

typedef Point Vector;

模长:就是向量的长度,记为|A|

double length(const Vector& v){
    return sqrt(v.x*v.x+v.y*v.y);
}

3、直线与线段

struct Line{
	Point s,e; //直线用两个点表示 
	Line(){}
	Line(Point _s,Point _e) {s=_s;e=_e;}
	pair<int,Point>operator &(const Line &b)const{ //判断两直线的关系 
		Point res=s;
		if(sgn((s-e)^(b.s-b.e))==0){
			if(sgn((s-b.e)^(b.s-b.e))==0) return make_pair(0,res); //两直线重合 
			else return make_pair(1,res); //两直线平行 
		}
		double t=((s-b.s)^(b.s-b.e))/((s-e)^(b.s-b.e));
		res.x+=(e.x-s.x)*t;
		res.y+=(e.y-s.y)*t;
		return make_pair(2,res); //两直线有交点,交点为res 
	}
};

顺便写了一个用法,大家可以自己动手试一试
在这里插入图片描述

int main(){
	Line lin[100];
	lin[1]=Line(Point(0,0),Point(1,1));
	lin[2]=Line(Point(0,9),Point(9,0)); 
	pair<int,Point>pr=lin[1]&lin[2];
	Point p=pr.second;
	if(pr.first==0) cout<<"重合"<<endl;
	else if(pr.first==1) cout<<"平行"<<endl;
	else cout<<"交点: ("<<p.x<<","<<p.y<<")"<<endl; 
	return 0;
}

4、点积与叉积的运用
点积很好理解,其实就是两个点的横坐标相乘+纵坐标相乘
我重点讲一下叉积:
叉积有几种比较常见的运用,比如
4.1、判断点在直线的哪一侧
在这里插入图片描述
通过这张图很清晰的可以看到顺时针叉积<0,逆时针叉积大于0
如果p在线段左侧 向量pa^pb>0;
如果p在线段右侧 向量pa^pb<0;

double operator ^(const Point &b)const{//叉积 
	return x*b.y-y*b.x;
} 
double operator *(const Point &b)const{//点积 
	return x*b.x+y*b.y;
} 

4.2、求多边形的面积
在这里插入图片描述
为了让大家更加熟悉叉积的操作我选了两道简单题
第一题:判断点在直线的哪一侧 本人博客
第二题:用叉积求多边形的面积 本人博客

讲到这里,计算几何最最最基础的部分大概是讲完了
现在我们来讲一下计算几何中的一些操作

5、两个点之间的距离
这个大家应该是都会的,不多说直接贴一下代码

//运用重载运算符 
double dist(Point a,Point b){
	return sqrt((a-b)*(a-b));
}
//未运用重载运算符 
double dist(Point a,Point b){
	return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
}

6、判断两条线段是否相交
直接看代码会比较抽象,大家可以用笔画一画,就很容易理解代码的含义
有些题目会考一些严格意义上的相交,我们就要把前四行的=符号去掉
我觉得有必要把这题找出来让大家练习一下 本人博客

bool inter(Line l1,Line l2){
	return
	max(l1.s.x,l1.e.x)>=min(l2.s.x,l2.e.x) &&
	max(l2.s.x,l2.e.x)>=min(l1.s.x,l1.e.x) &&
	max(l1.s.y,l1.e.y)>=min(l2.s.y,l2.e.y) &&
	max(l2.s.y,l2.e.y)>=min(l1.s.y,l1.e.y) &&
	sgn((l2.s-l1.e)^(l1.s-l1.e))*sgn((l2.e-l1.e)^(l1.s-l1.e))<=0 &&
	sgn((l1.s-l2.e)^(l2.s-l2.e))*sgn((l1.e-l2.e)^(l2.s-l2.e))<=0;
}

备注:这是去年我学长的讲课笔记,欢迎参考
判断线段相交核心代码(前四行判断是否在坐标轴上有重合,后两行判断是否跨立):
判断线段相交:(思想)
① 首先两条线段在X轴和Y轴上的投影必须必须有重合。
② 线段的两个端点必须在另一个线段的两侧(相互跨立)
所以假设固定一条线段,把另一条线段的两个点与线段做叉积操作,如果结果同号就说明两个点在同侧((C-A)X(C-B))X ((D-A)X(D-B)) > 0 (同侧)
(此处的X 为叉乘的意思)

7、判断直线与线段是否相交
判断线段与直线是否相交:(思想)
① 线段的两个端点必须在直线的两侧,与判断线段是否相交相似(相互跨立)
为什么不用判断投影是否重合??
答: 直线与线段在X轴和y轴上当且仅当直线与线段平行时才会仅仅在一条坐标轴上投影重合,但是当直线与线段平行时,线段与直线相互跨立是必定不存在的,由此得出结论:当判断线段与直线是否相交时只需判断是否跨立即可。

bool line_inter_seg(Line l1,Line l2) {
	return sgn((l2.s-l1.e)^(l1.s-l1.e))*sgn((l2.e-l1.e)^(l1.s-l1.e)) <= 0;
}

8、点到直线的距离
result表示点到直线上距离最近的点的坐标
证明过程如下:
在这里插入图片描述
通过上面截图,大家可以非常容易理解代码的含义

Point PointToLine(Point P,Line L){
	Point result;
	double t=((P-L.s)*(L.e-L.s))/((L.e-L.s)*(L.e-L.s));
	result.x=L.s.x+(L.e.x-L.s.x)*t;
	result.y=L.s.y+(L.e.y-L.s.y)*t;
	return result;
}

9、求点到线段最近的点,也就相当于求点到线段的最短距离
与上一点的证明过程类似
由点到直线的距离的计算我们可以得到关系比t,但与直线不同的是,这个点可能在坐标轴上与线段没有重合,即点到线段的距离就是点与线段的两个端点距离的最小值。此处对t作了一个特殊判断,当t∈[0,1]时,说明P点到线段所在直线的最短距离的点在线段上,就可以按照点到直线的距离函数的方法进行计算。当t不属于这个区间的时候,直接返回线段端点与P点较近的点即可。
请大家自己在证明一遍!!!

Point NearestPointToLineSeg(Point P,Line L){
	Point result;
	double t=((P-L.s)*(L.e-L.s))/((L.e-L.s)*(L.e-L.s));
	if(t>=0&&t<=1){
		result.x=L.s.x+(L.e.x-L.s.x)*t;
		result.y=L.s.y+(L.e.y-L.s.y)*t;
	}	
	else{	
		if(dist(P,L.s)<dist(P,L.e)) result=L.s;
		else result=L.e;
	}
	return result;
}

10、求多边形的面积
计算多边形面积代码(要求参数中点的集合必须是多边形顶点的顺时针或逆时针)

double CalcArea(Point p[],int n){
	double res=0;
	for(int i=0;i<n;i++) res+=(p[i]^p[(i+1)%n])/2;
	return fabs(res);
}

核心思想: 将多边形切割成多个小三角形,根据叉积的性质(叉积的值为两向量组成的平行四边形的面积),求出各个小三角形的面积相加。此处看似是直接对点进行遍历,其实是利用了坐标原点(0,0),p[i]^p[(i+1)%n 实际上是两个以原点为出发点的向量的叉积,再除二以后即为以向量为两条边的三角形的面积。

11、判断点是否在线段上

bool OnSeg(Point P,Line L){
	return
	sgn((L.s-P)^(L.e-P))==0 &&
	sgn((P.x-L.s.x)*(P.x-L.e.x))<=0 &&
	sgn((P.y-L.s.y)*(P.y-L.e.y))<=0;
}

核心思想: 当P点在线段上时(设线段上两个点为s和e点),P点与s和e点分别构成的向量应当是叉积为0的,同时P点的x与y应当介于s和e的x与y之间,满足所有条件即可证明点P在线段上。

12、判断点是否在凸多边形内
原理极其简单,就是我们传说中的暴力啦,自己看代码吧

int inConvexPoly(Point a,Point p[],int n){
	for(int i=0;i<n;i++){
		if(sgn((p[i]-a)^(p[(i+1)%n]-a))<0) return -1; //在凸多边形外 
		else if(OnSeg(a,Line(p[i],p[(i+1)%n]))) return 0; //在凸多边形边界上 
	}
	return 1; //在凸多边形内 
}

13、判断点是否在多边形内

int inPoly(Point p,Point poly[],int n){
	int cnt;
	Line ray,side;
	cnt=0; ray.s=p;
	ray.e.y=p.y; 
	ray.e.x=-100000000000.0; //构造一条一点p为起始点的射线 
	for(int i=0;i<n;i++){ //枚举边 
		side.s=poly[i];
		side.e=poly[(i+1)%n];
		if(OnSeg(p,side)) return 0; //在边界上 
		//如果平行轴则不用考虑 
		if(sgn(side.s.y-side.e.y)==0) continue;
		if(OnSeg(side.s,ray)){
			if(sgn(side.s.y-side.e.y)>0) cnt++;
		}
		else if(OnSeg(side.e,ray)){
			if(sgn(side.e.y-side.s.y)>0) cnt++;
		}
		else if(inter(ray,side)) cnt++;
	}
	if(cnt%2==1) return 1; //在多边形内 
	return -1; //在多边形外 
}

核心思想: 在欧式空间内,以一个点为顶点出发作一条射线,当射线穿越多边形时,可划分为两种状态,进入多边形和穿出多边形。当射线与多边形的接触点为偶数时(包括0),则说明点在多边形外,反之则在多边形内部。 此处有三个状态需要进行特殊处理:
① 射线的顶点在多边形上。
② 射线经过了多边形的一个顶点。
③ 多边形的边与射线平行。
函数中cnt变量保存的是射线与多边形的接触点的数量。同时为避免重复运算,当射线经过多边形的一个顶点时,仅当经过的点是循环中线段side的两个端点中纵坐标y较大的点时才算有效。(注: 函数中ray保存的是以p为顶点方向为负方向的射线,side保存的是多边形的一条边,是一个线段)

14、判断多边形是否为凸多边形

bool isconvex(Point poly[],int n){
	bool s[3];
	memset(s,false,sizeof(s));
	for(int i=0;i<n;i++){
		s[sgn((poly[(i+1)%n]-poly[i])^(poly[(i+2)%n]-poly[i]))+1]=true;
		if(s[0] && s[2]) return false;
	}
	return true;
}

核心思想: 当一个多边形为凸多边形时,以poly[i]为出发点到poly[i+1]和ploy[i+2] 构成的两个向量的叉积的符号应当是保持一致的。

15、将线段往左移动一定距离

void change(Point a,Point b,Point &c,Point &d,double p){//将线段ab往左移动距离p
    double len=dist(a,b);
    double dx=(a.y-b.y)*p/len;
    double dy=(b.x-a.x)*p/len;
    c.x=a.x+dx; c.y=a.y+dy;
    d.x=b.x+dx; d.y=b.y+dy;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值