首先我们要理解什么是向量,向量就是有大小和方向的量。在平面坐标系中,向量用x,y表示。等于向量起点到终点的位移。以下是它们的常用定义:
[cpp] view plain copy print ?
- struct Point
- {
- double x,y;
- Point(double x=0,double y=0):x(x),y(y){}
- };
- typedef Point Vector;
-
-
- Point operator+(Point A,Point B)
- {
- return Point(A.x+B.x,A.y+B.y);
- }
-
-
- Point operator-(Point A,Point B)
- {
- return Point(A.x-B.x,A.y-B.y);
- }
-
-
-
- Point operator*(Point A,double p)
- {
- return Point(A.x*p,A.y*p);
- }
-
-
-
- Point operator/(Point A,double p)
- {
- return Point(A.x/p,A.y/p);
- }
- double eps=1e-10;
-
-
- int dcmp(double x)
- {
- if(fabs(x)<eps)return 0;else return x<0?-1:1;
- }
-
-
- bool operator==(const Point& a,const Point &b)
- {
- return dcmp(a.x-b.x)==0&&dcmp(a.y-b.y)==0;
- }
-
- int main()
- {
- Point a,b,c;
- double p=2;
- a.x=4;
- a.y=3;
- b.x=2;
- b.y=5;
- c=a+b;
- c=a-b;
- c=a/p;
- c=a*p;
- return 0;
- }
struct Point
{
double x,y;
Point(double x=0,double y=0):x(x),y(y){}//构造函数
};
typedef Point Vector;
//向量+向量=向量,点+向量=点
Point operator+(Point A,Point B)
{
return Point(A.x+B.x,A.y+B.y);
}
//点-点=向量
Point operator-(Point A,Point B)
{
return Point(A.x-B.x,A.y-B.y);
}
//向量*数=向量
Point operator*(Point A,double p)
{
return Point(A.x*p,A.y*p);
}
//向量/数=向量
Point operator/(Point A,double p)
{
return Point(A.x/p,A.y/p);
}
double eps=1e-10;
//如果等于0,返回0,小于0返回-1,大于0,返回1
int dcmp(double x)
{
if(fabs(x)<eps)return 0;else return x<0?-1:1;
}
//判断是否相等
bool operator==(const Point& a,const Point &b)
{
return dcmp(a.x-b.x)==0&&dcmp(a.y-b.y)==0;
}
int main()
{
Point a,b,c;
double p=2;
a.x=4;
a.y=3;
b.x=2;
b.y=5;
c=a+b;//向量+向量=向量
c=a-b;//点-点=向量
c=a/p;//向量/数=向量
c=a*p;//向量*数=向量
return 0;
}
基本运算
点积是两个向量v和w的点积等于二者长度的乘积在乘上它们夹角的余弦。如果两向量垂直,点积就等于0。两个向量OA和OB的点积等于xa*xb+ya*yb。下面是点积计算方法,以及用点积计算向量长度和夹角的函数。
[cpp] view plain copy print ?
-
- double Dot(Point A,Point B)
- {
- return A.x*B.x+A.y*B.y;
- }
-
- double Lenth(Point A)
- {
- return sqrt(Dot(A,A));
- }
-
- double Angle(Point A,Point B)
- {
- return acos(Dot(A,B)/Lenth(A)/Lenth(B));
- }
//点积计算方法
double Dot(Point A,Point B)
{
return A.x*B.x+A.y*B.y;
}
//点到原点的长度
double Lenth(Point A)
{
return sqrt(Dot(A,A));
}
//角度计算
double Angle(Point A,Point B)
{
return acos(Dot(A,B)/Lenth(A)/Lenth(B));
}
叉积就是两个向量v和w组成的三角形的有向面积的两倍。叉积的计算方法及三角形面积的两倍的计算方法如下:
[cpp] view plain copy print ?
-
- double Cross(Point A,Point B)
- {
- return A.x*B.y-A.y*B.x;
- }
-
-
- double Area(Point A,Point B,Point C)
- {
- return Cross(B-A,C-A);
- }
//就算OA和OB的叉积
double Cross(Point A,Point B)
{
return A.x*B.y-A.y*B.x;
}
//计算三角形的面积的两倍
double Area(Point A,Point B,Point C)
{
return Cross(B-A,C-A);
}
向量的旋转。向量可以绕起点旋转,公式为x'=x*cosa-y*sina,y'=x*sina+y*cosa。其中a为逆时针旋转的角。代码如下:
[cpp] view plain copy print ?
-
- Point Rotate(Point A,double rad)
- {
- return Point(A.x*cos(rad)-A.y*sin(rad),A.x*sin(rad)+A.y*cos(rad));
- }
//向量的旋转
Point Rotate(Point A,double rad)//rad是弧度
{
return Point(A.x*cos(rad)-A.y*sin(rad),A.x*sin(rad)+A.y*cos(rad));
}
下面的函数计算向量的单位法线,即左转90度以后把长度归一化。
[cpp] view plain copy print ?
-
- Point Normal(Point A)
- {
- double l=Lenth(A);
- return Point(-A.y/l,A.x/l);
- }
//计算向量单位法线
Point Normal(Point A)
{
double l=Lenth(A);
return Point(-A.y/l,A.x/l);
}
点和直线
直线上一点可以用直线上一点p0和方向向量v来表示。直线上所有点p=p0+t*v.其中t为参数。如果已知直线上的两个不同点A和B,则方向向量为B-A,所以参数方程A+(B-A)*t。对于直线t没有限制,射线t>0,线段0<t<1。
直线交点。设直线分别为P+tv和Q+tw,设向量u=P-Q,交点在第一条直线的参数为t1,第二条直线的参数为t2,则x和y坐标可以列出一个方程,解得:
t1=cross(w,u)/cross(v,w),t2=cross(v,u)/cross(v,w)。
代码如下:
[cpp] view plain copy print ?
-
- Point GetLineIntersection(Point P,Point V,Point Q,Point W)
- {
- Point u=P-Q;
- double t=Cross(W,u)/Cross(V,W);
- return P+V*t;
- }
//直线交点公式
Point GetLineIntersection(Point P,Point V,Point Q,Point W)
{
Point u=P-Q;
double t=Cross(W,u)/Cross(V,W);
return P+V*t;
}
点到直线的距离。点到直线的距离是一个常用函数,可以用叉积算出,即用平行四边形的面积除以底,代码如下:
[cpp] view plain copy print ?
-
- double DistanceToLine(Point P,Point A,Point B)
- {
- Point v1=B-A,v2=P-A;
- return fabs(Cross(v1,v2))/Lenth(v1);
- }
//点到直线的距离
double DistanceToLine(Point P,Point A,Point B)
{
Point v1=B-A,v2=P-A;
return fabs(Cross(v1,v2))/Lenth(v1);
}
点到线段的距离有两种情况,代码如下:
[cpp] view plain copy print ?
-
- double DistanceToSegment(Point P,Point A,Point B)
- {
- if(A==B) return Lenth((P-A));
- Point v1=B-A,v2=P-A,v3=P-B;
- if(dcmp(Dot(v1,v2))<0)return Lenth(v2);
- else if(dcmp(Dot(v1,v3))>0)return Lenth((v3));
- else return fabs(Cross(v1,v2))/Lenth(v1);
- }
//点到线段的距离
double DistanceToSegment(Point P,Point A,Point B)
{
if(A==B) return Lenth((P-A));
Point v1=B-A,v2=P-A,v3=P-B;
if(dcmp(Dot(v1,v2))<0)return Lenth(v2);
else if(dcmp(Dot(v1,v3))>0)return Lenth((v3));
else return fabs(Cross(v1,v2))/Lenth(v1);
}
点在直线上的投影,代码如下:
[cpp] view plain copy print ?
-
- Point GetLineProjecton(Point P,Point A,Point B)
- {
- Point v=B-A;
- return A+v*(Dot(v,P-A)/Dot(v,v));
- }
//点P在直线AB上的投影
Point GetLineProjecton(Point P,Point A,Point B)
{
Point v=B-A;
return A+v*(Dot(v,P-A)/Dot(v,v));
}
线段相交判定。给定两条线段,判断是否相交。我们定义“规范相交”为两线段恰好有一个公共点,且不在任何一条线段的端点。线段规范相交的充要条件是:每条线段的两个端点都在另一条线段的两侧。代码如下:
[cpp] view plain copy print ?
-
- bool SegmentProperIntersection(Point a1,Point a2,Point b1,Point b2)
- {
- double c1=Cross(a2-a1,b1-a1),c2=Cross(a2-a1,b2-a1),
- c3=Cross(b2-b1,a1-b1),c4=Cross(b2-b1,a2-b1);
- return dcmp(c1)*dcmp(c2)<0&&dcmp(c3)*dcmp(c4)<0;
- }
//判断线段相交(不含端点)
bool SegmentProperIntersection(Point a1,Point a2,Point b1,Point b2)
{
double c1=Cross(a2-a1,b1-a1),c2=Cross(a2-a1,b2-a1),
c3=Cross(b2-b1,a1-b1),c4=Cross(b2-b1,a2-b1);
return dcmp(c1)*dcmp(c2)<0&&dcmp(c3)*dcmp(c4)<0;
}
如果允许在端点出相交,情况就比较复杂了,有可能共线,还有可能某个端点在另外一条线段上。为了判断上述情况是否发生,还需要如下一段判断一个点是否在一条线段上(不含端点)的代码:
[cpp] view plain copy print ?
-
- bool OnSegment(Point p,Point a1,Point a2)
- {
- return dcmp(Cross(a1-p,a2-p))==0&&dcmp(Dot(a1-p,a2-p));
- }
//判断一个点P是否在一条线段a1a2上
bool OnSegment(Point p,Point a1,Point a2)
{
return dcmp(Cross(a1-p,a2-p))==0&&dcmp(Dot(a1-p,a2-p));
}
多边形
计算多边形的面积。如果多变形是凸的,可以从第一个顶点出发把凸多边形分成n-2个三角形,然后把面积加起来。代码如下:
[cpp] view plain copy print ?
-
- double ConvexPolygonArea(Point* p,int n)
- {
- double area=0;
- for(int i=1;i<n-1;i++)
- area+=Cross(p[i]-p[0],p[i+1]-p[0]);
- return area/2;
- }
//计算多边形的面积
double ConvexPolygonArea(Point* p,int n)
{
double area=0;
for(int i=1;i<n-1;i++)
area+=Cross(p[i]-p[0],p[i+1]-p[0]);
return area/2;
}
可以另取p[0]点为划分顶点,一方面可以少算两个叉积,另一方面也减少乘法溢出的可能性,还不用特殊处理。代码如下:
[cpp] view plain copy print ?
- double PolygonArea(Point* p,int n)
- {
- double area=0;
- for(int i=1;i<n-1;i++)
- area+=Cross(p[i]-p[0],p[i+1]-p[0]);
- return area/2;
- }
- int main()
- {
- Point a[4];
- a[0].x=0;
- a[0].y=0;
- a[1].x=3;
- a[1].y=0;
- a[2].x=3;
- a[2].y=3;
- a[3].x=0;
- a[3].y=3;
- double area=PolygonArea(a,4);
- printf("%lf",area);
- return 0;
- }