与圆有关算法总结

一、两圆位置关系

两圆间位置关系有五种,相离,外切,相交,内切,内含,利用圆心距d和半径差以及半径和的大小关系来判定。

二、三角形内切圆

三角形内切圆的圆心是三条角平分线的交点,因此可以转化为求直线交点问题,任取两角平分线交点即为圆心,得到圆心后利用叉乘求出圆心到边的距离就是半径。

在引入向量这个概念后求角平分线也十分简单,先求两条边的向量,然后全部单位化,最后作向量加和就是角平分线的向量。

三、三角形外接圆

三角形外接圆的圆心是三条垂直平分线的交点,同样转为求两直线交点问题,因此要先求出至少两条垂直平分线。垂直平分线的起点很容易获得,就是边上的中点,然后需要将对应边向量旋转90°后得到垂向量,垂直平分线的终点坐标就是起点加该垂向量。得到两条垂直平分线后求个交点就是外接圆的圆心,最后用两点间距离公式求出半径。

四、直线和圆的交点

首先要知道给定的直线不一定和圆有交点,需要先用圆心到直线距离和半径比对一下,若该距离大于半径那根本不存在交点,直接退出就行。因此只考虑存在交点时的情况,如下图所示:

直线l与圆O的交点为A和B,过圆心O引一条AB的垂线,交AB于点P。根据现有条件可以求出点到直线距离OP的长度,进而得到AP长度。由于直线l是已知的,那么向量BA也是已知的,向量BA和向量PA是同向的,只是长度不同,因此向量PA也可以求出。将向量BA旋转90°可得到OP的同向向量,又知道OP长度,所以向量OP也已知。这时候向量OA就已知了,它等于OP+PA。然后O点坐标加向量OA得到A点坐标,B点坐标也是同理求出。

五、两圆交点

当两圆相离或者内含时是不存在交点的,所以只考虑剩下的相交或相切的情况。如下图所示,两圆交点为A和B,连接两圆心交AB于点P,显然AB垂直于O1O2。

设圆心距为d,O1P长度为x,则PO2长度为d-x,用勾股定理列出图中公式,解得x值,同时AP长度也已知。向量O1P是O1O2同向向量,又知道O1P长度,可以得到向量O1P,同理得到向量PA,最终得到向量O1A,这样就求出A点坐标了。B点坐标同理求得。

六、过定点圆的切线

依然是有一些特殊情况不用处理,当定点在圆内时不存在切线,所以只考虑点在圆外的情况,如下图所示。

PA切圆O于点A,PB切圆O于点B,AB交PO于点Q,显然PO垂直于AB。设QO长度为x,三角形AQO相似于三角形PAO,于是可以求出x值。之后的步骤和上面几个问题处理方式一致,求出向量OQ和向量QA,然后得到向量OA,这样就求出A点坐标了。

七、两圆公切线

这个问题是比较复杂的,情况比较多,首先要知道两圆相离时有4条公切线,外切时有3条,相交时有2条,内切时有1条,内含时有0条,因此要根据两圆位置关系来分类讨论。

以两圆相离时为例,其他情况都是类似的。

先求两条外切线,如下图所示。

有一个很有用的结论:两条外切线交点一定在两圆心连线上。如上图,点P一定在O1O2上。如果求出点P坐标那这个问题就可以转为六、过定点圆的切线问题了。如果把上一个问题实现过程封装到函数里,在解决这个问题时调用几次函数就可以了。

接下来考虑怎么求P点坐标,显然图中有一组相似三角形,之后得到比例关系:A2O2/A1O1 = O2P/O1P,从而求出O2P的长度,这样就得到向量O2P了,也就得到了点P坐标。

对于两条内切线步骤和上面基本一致,也是先找到两条切线交点P,然后转为六、过定点圆的切线问题,求P点坐标时也是发现一组相似三角形利用比例关系求得。

八、圆和多边形面积交

类似求多边形面积的方法,将多边形面积转化为若干个有向三角形的面积和,之后求每个有向三角形和圆的面积交,最后加和就是答案。

九、两圆相交面积

同样存在一些特殊情况,两圆相离或外切时面积为0,内切或内含时面积为较小的圆面积,所以只考虑相交时的情况,如下图所示。

两圆交点分别为A和B,连接AB交O1O2于点P,显然O1O2垂直于AB,待求面积为紫色区域。AB将紫色区域分为两部分,可以依次求每部分的面积,对于每部分面积就是扇形面积减去三角形面积。之后要做的就是求出各点坐标了,这个问题其实就是五、两圆交点,得到交点坐标后各边长就已知了,用初中数学知识解决即可。

十、模板

代码基于kuangbin的计算几何模板,又添加了一些我自己的理解,同时添加了求两圆公切线的函数。

//圆
struct circle{
	Point p;//圆心
	double r;//半径
	circle(){}
	circle(Point _p,double _r){
		p = _p;
		r = _r;
	}
	circle(double x,double y,double _r){
		p = Point(x,y);
		r = _r;
	}
	//`三角形的外接圆`
	//`需要Point的+ /  rotate()  以及Line的crosspoint()`
	//`利用两条边的中垂线得到圆心`
	//`测试:UVA12304`
	circle(Point a,Point b,Point c){
		Line u = Line((a+b)/2,((a+b)/2)+((b-a).rotleft()));
		Line v = Line((b+c)/2,((b+c)/2)+((c-b).rotleft()));
		p = u.crosspoint(v);
		r = p.distance(a);
	}
	//`三角形的内切圆`
	//`参数bool t没有作用,只是为了和上面外接圆函数区别`
	//`测试:UVA12304`
	circle(Point a,Point b,Point c,bool t){
		Line u,v;
		double m = atan2(b.y-a.y,b.x-a.x), n = atan2(c.y-a.y,c.x-a.x);
		u.s = a;
		u.e = u.s + Point(cos((n+m)/2),sin((n+m)/2));
		v.s = b;
		m = atan2(a.y-b.y,a.x-b.x) , n = atan2(c.y-b.y,c.x-b.x);
		v.e = v.s + Point(cos((n+m)/2),sin((n+m)/2));
		p = u.crosspoint(v);
		r = Line(a,b).dispointtoseg(p);
	}
	//输入
	void input(){
		p.input();
		scanf("%lf",&r);
	}
	//输出
	void output(){
		printf("%.2lf %.2lf %.2lf\n",p.x,p.y,r);
	}
	bool operator == (circle v){
		return (p==v.p) && sgn(r-v.r)==0;
	}
	bool operator < (circle v)const{
		return ((p<v.p)||((p==v.p)&&sgn(r-v.r)<0));
	}
	//面积
	double area(){
		return pi*r*r;
	}
	//周长
	double circumference(){
		return 2*pi*r;
	}
	//`点和圆的关系`
	//`0 圆外`
	//`1 圆上`
	//`2 圆内`
	int relation(Point b){
		double dst = b.distance(p);
		if(sgn(dst-r) < 0)return 2;
		else if(sgn(dst-r)==0)return 1;
		return 0;
	}
	//`线段和圆的关系`
	//`比较的是圆心到线段的距离和半径的关系`
	int relationseg(Line v){
		double dst = v.dispointtoseg(p);
		if(sgn(dst-r) < 0)return 2;
		else if(sgn(dst-r) == 0)return 1;
		return 0;
	}
	//`直线和圆的关系`
	//`比较的是圆心到直线的距离和半径的关系`
	int relationline(Line v){
		double dst = v.dispointtoline(p);
		if(sgn(dst-r) < 0)return 2;
		else if(sgn(dst-r) == 0)return 1;
		return 0;
	}
	//`两圆的关系`
	//`5 相离`
	//`4 外切`
	//`3 相交`
	//`2 内切`
	//`1 内含`
	//`需要Point的distance`
	//`测试:UVA12304`
	int relationcircle(circle v){
		double d = p.distance(v.p);
		if(sgn(d-r-v.r) > 0)return 5;
		if(sgn(d-r-v.r) == 0)return 4;
		double l = fabs(r-v.r);
		if(sgn(d-r-v.r)<0 && sgn(d-l)>0)return 3;
		if(sgn(d-l)==0)return 2;
		if(sgn(d-l)<0)return 1;
	}
	//`求两个圆的交点,返回0表示没有交点,返回1是一个交点,2是两个交点`
	//可能需要特判两圆完全重合 
	//`需要relationcircle`
	//`测试:UVA12304`
	int pointcrosscircle(circle v,Point &p1,Point &p2){
		int rel = relationcircle(v);
		if(rel == 1 || rel == 5)return 0;
		double d = p.distance(v.p);//圆心距 
		double l = (d*d+r*r-v.r*v.r)/(2*d);//两次勾股定理求出圆心到两交点连线距离 
		double h = sqrt(r*r-l*l);//勾股定理求出两交点距离一半 
		Point tmp = p + (v.p-p).trunc(l);//两圆心连线与两交点连线的交点 
		p1 = tmp + ((v.p-p).rotleft().trunc(h));//向量左侧的交点 
		p2 = tmp + ((v.p-p).rotright().trunc(h));//向量右侧的交点 
		if(rel == 2 || rel == 4)
			return 1;
		return 2;
	}
	//`求直线和圆的交点,返回交点个数`
	//关键是找到圆心在直线上的投影点 
	int pointcrossline(Line v,Point &p1,Point &p2){
		if(!(*this).relationline(v))return 0;
		Point a = v.lineprog(p);
		double d = v.dispointtoline(p);
		d = sqrt(r*r-d*d);
		if(sgn(d) == 0){
			p1 = a;
			p2 = a;
			return 1;
		}
		p1 = a + (v.e-v.s).trunc(d);
		p2 = a - (v.e-v.s).trunc(d);
		return 2;
	}
	//`得到过a,b两点,半径为r1的两个圆`
	int gercircle(Point a,Point b,double r1,circle &c1,circle &c2){
		circle x(a,r1),y(b,r1);
		int t = x.pointcrosscircle(y,c1.p,c2.p);
		if(!t)return 0;
		c1.r = c2.r = r;
		return t;
	}
	//`得到与直线u相切,过点q,半径为r1的圆`
	//`测试:UVA12304`
	int getcircle(Line u,Point q,double r1,circle &c1,circle &c2){
		double dis = u.dispointtoline(q);
		if(sgn(dis-r1*2)>0)return 0;
		if(sgn(dis) == 0){
			c1.p = q + ((u.e-u.s).rotleft().trunc(r1));
			c2.p = q + ((u.e-u.s).rotright().trunc(r1));
			c1.r = c2.r = r1;
			return 2;
		}
		Line u1 = Line((u.s + (u.e-u.s).rotleft().trunc(r1)),(u.e + (u.e-u.s).rotleft().trunc(r1)));
		Line u2 = Line((u.s + (u.e-u.s).rotright().trunc(r1)),(u.e + (u.e-u.s).rotright().trunc(r1)));
		circle cc = circle(q,r1);
		Point p1,p2;
		if(!cc.pointcrossline(u1,p1,p2))cc.pointcrossline(u2,p1,p2);
		c1 = circle(p1,r1);
		if(p1 == p2){
			c2 = c1;
			return 1;
		}
		c2 = circle(p2,r1);
		return 2;
	}
	//`同时与直线u,v相切,半径为r1的圆`
	//`测试:UVA12304`
	int getcircle(Line u,Line v,double r1,circle &c1,circle &c2,circle &c3,circle &c4){
		if(u.parallel(v))return 0;//两直线平行
		Line u1 = Line(u.s + (u.e-u.s).rotleft().trunc(r1),u.e + (u.e-u.s).rotleft().trunc(r1));
		Line u2 = Line(u.s + (u.e-u.s).rotright().trunc(r1),u.e + (u.e-u.s).rotright().trunc(r1));
		Line v1 = Line(v.s + (v.e-v.s).rotleft().trunc(r1),v.e + (v.e-v.s).rotleft().trunc(r1));
		Line v2 = Line(v.s + (v.e-v.s).rotright().trunc(r1),v.e + (v.e-v.s).rotright().trunc(r1));
		c1.r = c2.r = c3.r = c4.r = r1;
		c1.p = u1.crosspoint(v1);
		c2.p = u1.crosspoint(v2);
		c3.p = u2.crosspoint(v1);
		c4.p = u2.crosspoint(v2);
		return 4;
	}
	//`同时与不相交圆cx,cy相切,半径为r1的圆`
	//`测试:UVA12304`
	int getcircle(circle cx,circle cy,double r1,circle &c1,circle &c2){
		circle x(cx.p,r1+cx.r),y(cy.p,r1+cy.r);
		int t = x.pointcrosscircle(y,c1.p,c2.p);
		if(!t)return 0;
		c1.r = c2.r = r1;
		return t;
	}
	//`过一点作圆的切线(先判断点和圆的关系)`
	//连接圆心到点以及两切点,利用相似三角形求出圆心到两切点连线距离 
	//`测试:UVA12304`
	int tangentline(Point q,Line &u,Line &v){
		int x = relation(q);
		if(x == 2)//点在圆内 
			return 0;
		if(x == 1)//点在圆上 
		{
			u = Line(q,q + (q-p).rotleft());
			v = u;
			return 1;
		}
		//点在圆外 
		double d = p.distance(q);//点到圆心的距离 
		double l = r*r/d;//利用相似三角形得出圆心到两切点连线距离  
		double h = sqrt(r*r-l*l);//勾股定理求两切点距离一半 
		u = Line(q,p + ((q-p).trunc(l) + (q-p).rotleft().trunc(h)));
		v = Line(q,p + ((q-p).trunc(l) + (q-p).rotright().trunc(h)));
		return 2;
	}
	//`求两圆相交的面积` 
	double areacircle(circle v){
		int rel = relationcircle(v);
		if(rel >= 4)return 0.0;//外切或相离 
		if(rel <= 2)return min(area(),v.area());//内切或内含 
		//只处理相交的情况
		//两交点连线把相交面积分成两部分,每一部分都可以用扇形减三角形得到 
		double d = p.distance(v.p);//圆心距 
		double hf = (r+v.r+d)/2.0;//海伦公式求三角形面积 
		double ss = 2*sqrt(hf*(hf-r)*(hf-v.r)*(hf-d));//多余的两个三角形面积 
		double a1 = acos((r*r+d*d-v.r*v.r)/(2.0*r*d)); 
		a1 = a1*r*r;//扇形面积1
		double a2 = acos((v.r*v.r+d*d-r*r)/(2.0*v.r*d));
		a2 = a2*v.r*v.r;//扇形面积2
		return a1+a2-ss;
	}
	//`求圆和三角形pab的相交面积`
	//`测试:POJ3675 HDU3982 HDU2892`
	double areatriangle(Point a,Point b){
		if(sgn((p-a)^(p-b)) == 0)return 0.0;//三角形pab面积为0时 
		Point q[5];
		int len = 0;
		q[len++] = a;
		Line l(a,b);
		Point p1,p2;
		if(pointcrossline(l,q[1],q[2])==2){
			if(sgn((a-q[1])*(b-q[1]))<0)q[len++] = q[1];
			if(sgn((a-q[2])*(b-q[2]))<0)q[len++] = q[2];
		}
		q[len++] = b;
		if(len == 4 && sgn((q[0]-q[1])*(q[2]-q[1]))>0)swap(q[1],q[2]);
		double res = 0;
		for(int i = 0;i < len-1;i++){
			if(relation(q[i])==0||relation(q[i+1])==0){
				double arg = p.rad(q[i],q[i+1]);
				res += r*r*arg/2.0;
			}
			else{
				res += fabs((q[i]-p)^(q[i+1]-p))/2.0;
			}
		}
		return res;
	}
	//两圆公切线 
	//返回值为公切线条数,-1表示无数条公切线 
	//线段起点都是大圆上切点,终点都是小圆v上切点(除了存在公切点时)
	//测试:Aizu - CGL_7_G  
	int tangentcircle(circle v, Line l[4])
	{
		int t = relationcircle(v);
		if(r < v.r)//使当前圆为半径大者 
			return v.tangentcircle(*this, l);
		if(t == 5)//相离 
		{
			//特判半径相同情况 
			if(sgn(r-v.r) == 0)
			{
				l[0].s = p+(v.p-p).rotleft().trunc(r);
				l[0].e = l[0].s+v.p-p;
				l[1].s = p+(v.p-p).rotright().trunc(r);
				l[1].e = l[1].s+v.p-p;
				Point q = (p+v.p)/2;
				Line t1, t2;
				tangentline(q, t1, t2);
				l[2].s = t1.e;
				l[3].s = t2.e;
				v.tangentline(q, t1, t2);
				l[2].e = t1.e;
				l[3].e = t2.e; 
			} 
			else
			{
				double d = p.distance(v.p);
				Point q = v.p+(v.p-p).trunc(v.r*d/(r-v.r));
				Line t1, t2;
				tangentline(q, t1, t2);
				l[0].s = t1.e;
				l[1].s = t2.e;
				v.tangentline(q, t1, t2);
				l[0].e = t1.e;
				l[1].e = t2.e;
				q = p+(v.p-p).trunc(r*d/(r+v.r));
				tangentline(q, t1, t2);
				l[2].s = t1.e;
				l[3].s = t2.e;
				v.tangentline(q, t1, t2);
				l[2].e = t1.e;
				l[3].e = t2.e; 
			}
		}
		else if(t == 4)//外切
		{
			//特判半径相同情况 
			if(sgn(r-v.r) == 0)
			{
				l[0].s = p+(v.p-p).rotleft().trunc(r);
				l[0].e = l[0].s+v.p-p;
				l[1].s = p+(v.p-p).rotright().trunc(r);
				l[1].e = l[1].s+v.p-p;
				Point q = (p+v.p)/2;
				Line t1, t2;
				tangentline(q, t1, t2);
				l[2] = t1;
			}
			else
			{
				double d = p.distance(v.p);
				Point q = v.p+(v.p-p).trunc(v.r*d/(r-v.r));
				Line t1, t2;
				tangentline(q, t1, t2);
				l[0].s = t1.e;
				l[1].s = t2.e;
				v.tangentline(q, t1, t2);
				l[0].e = t1.e;
				l[1].e = t2.e;
				q = p+(v.p-p).trunc(r);
				tangentline(q, t1, t2);
				l[2] = t1;
			} 
		} 
		else if(t == 3)//相交
		{
			//特判半径相同情况 
			if(sgn(r-v.r) == 0)
			{
				l[0].s = p+(v.p-p).rotleft().trunc(r);
				l[0].e = l[0].s+v.p-p;
				l[1].s = p+(v.p-p).rotright().trunc(r);
				l[1].e = l[1].s+v.p-p;
			}
			else
			{
				double d = p.distance(v.p);
				Point q = v.p+(v.p-p).trunc(v.r*d/(r-v.r));
				Line t1, t2;
				tangentline(q, t1, t2);
				l[0].s = t1.e;
				l[1].s = t2.e;
				v.tangentline(q, t1, t2);
				l[0].e = t1.e;
				l[1].e = t2.e;
			} 
		} 
		else if(t == 2)//内切
		{
			//特判半径相同情况 
			if(sgn(r-v.r) == 0)
				return -1;//无数条公切线
			else
			{
				Point q = p+(v.p-p).trunc(r);
				Line t1, t2;
				tangentline(q, t1, t2);
				l[0] = t1;
			} 
		} 
		//内含的情况直接返回0 
		return t-1;
	}
};

十一、例题

[两圆关系]Intersection Aizu CGL_7_A

[精度][三角形内切圆]Incircle of a Triangle Aizu CGL_7_B

[三角形外接圆]Circumscribed Circle of a Triangle Aizu CGL_7_C

[直线和圆交点]Cross Points of a Circle and a Line Aizu CGL_7_D

[两圆交点]Cross Points of Circles Aizu CGL_7_E

[过定点圆的切线]Tangent to a Circle Aizu CGL_7_F

[两圆公切线]Common Tangent Aizu CGL_7_G

[圆和多边形面积交]Intersection of a Circle and a Polygon Aizu CGL_7_H

[精度][两圆相交面积]Area of Intersection between Two Circles Aizu CGL_7_I

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值