点与向量
用程序求解几何问题,先要想办法用编程中的数据结构来表示几何对象。这里,使用向量就是解决办法之一。
我们将既有大小又有方向的量称为向量。相对地,只有大小没有方向的量称为标量。为了用数据结构表示向量,将向量考虑成从原点O(0, 0)指向对象点P(x, y)的有向线段。
向量落实在纸面上容易给人带来一种误解,觉得向量可以表示平面上的线段。但实际上,向量只具有大小和方向,而线段却要由两个端点确定,因此用向量表示线段还需要另外规定一个起点。
平面几何中最简单的元素是点(x, y),我们可以用如下所示结构体或类来实现:
表示点的结构体:
struct Point { double x, y; };
由于向量也可以仅用一个点来定义,所以我们用和点完全相同的数据结构来表示向量。这里的typedef用来给已有数据类型创建新的名称,这样Point和Vector虽然表示同一个数据结构,却可以视情况分开使用(用于不同意义的函数、变量等)。
表示向量的结构体:
typedef Point Vector;
线段与直线
我们可以用包含两个点(起点p1和终点p2)的结构体或类来表示线段。
表示线段的结构体:
struct Segment {
Point p1, p2;
};
要注意区分线段与直线。线段是由两个端点及其间距离定义的具有一定长度的线,而直线是通过两个点且长度无限的线。也就是说,直线由两个不同的点定义,并不具有端点。
直线和线段可以用相同的方法实现。我们用与线段相同的数据结构来表示直线。
表示直线的结构体:
typedef Segment Line;
圆
圆可以用包含圆心c和半径r的结构体或类来表示。
表示圆的类:
class Circle {
public:
Point c;
double r;
Circle(Point c = Point(), double r = 0.0): c(c), r(r) {}
};
多边形
多边形可以用点的序列来表示。
多边形的表示:
typedef vector<Point> Polygon;
向量的基本运算
向量的几种基本运算:
两个向量的和(sum)a + b
两个向量的差(difference)a - b
一个向量的标量倍(scalar multiplication)ka
向量运算可以定义为函数。我们为了更直观地对点对象进行操作,选择将点的结构体或类之间的运算定义为运算符。C++允许我们对运算符进行定义。
定义点/向量间的运算符:
double x, y;
Point operator + (Point &p) {
return Point(x + p.x, y + p.y);
}
Point operator - (Point &p) {
return Point(x - p.x, y - p.y);
}
Point operator * (double k) {
return Point(x * k, y * k);
}
x、y表示相应类中的点,p表示对象点。
定义运算符之后,向量间的运算便可以用下面的方法描述了。
向量间的运算示例:
Vector a, b, c, d;
c = a - b;//向量的差
d = a * 2.0;//向量的标量倍
向量的大小
向量 a = ( a x , a y ) a=(a_x,a_y) a=(ax,ay)的大小|a|(absolute,a的绝对值)就是原点到表示向量的点的距离。除此之外,还有表示向量大小的平方的概念,称为范数(norm)。
我们可以这样实现一个以向量为参数,返回该向量大小及范数的函数。
向量的范数及大小:
double norm(Vector a) {
return a.x * a.x + a.y * a.y;
}
double abs(Vector a) {
return sqrt(norm(a));
}
请注意,此abs与C/C++中的abs函数(用于返回给定数值的绝对值)不同。二者由引用的参数类型区分。
另外,我们还可以视情况将abs、norm等向量间的基本运算以类的成员函数形式来实现。
Point/Vector类
下述程序是实现Point类(Vector类)的例子,其中包含了我们前面讲到的运算符等内容。
Point类:
#define EPS (1e - 10)
#define equals(a, b) (fabs((a) - (b)) < EPS)
class Point {
public:
double x, y;
Point(double x = 0, double y = 0): x(x), y(y) {}
Point operator + (Point p) { return Point(x + p.x, y + p.y); }
Point operator - (Point p) { return Point(x - p.x, y - p.y); }
Point operator * (double a) { return Point(a * x, a * y); }
Point operator / (double a) { return Point(x / a, y / a); }
double abs() { return sqrt(norm()); }
double norm() { return x * x + y * y; }
bool operator < (const Point &p) const {
return x != p.x ? x < p.x : y < p.y;
}
bool operator == (const Point &p) const {
return fabs(x - p.x) < EPS && fabs(y - p.y) < EPS;
}
};
typedef Point Vector;
向量的内积
利用向量内积/外积的几何性质,我们可以创建许多“零件”(程序)来求解计算几何学的相关问题。
设向量a、b的夹角为θ(0 ≤ θ ≤ 180),那么a、b的内积(dot product)a · b = |a||b|cosθ。
用
a
=
(
a
x
,
a
y
)
a=(a_x,a_y)
a=(ax,ay),
b
=
(
b
x
,
b
y
)
b=(b_x,b_y)
b=(bx,by)的形式表示两个向量,根据余弦定理可推导出二维平面上2个向量a、b的内积可以表示为
a · b = |a||b|cosθ =
a
x
∗
b
x
+
a
y
∗
b
y
a_x * b_x + a_y * b_y
ax∗bx+ay∗by
下面是一个求向量a、b内积的程序。内积dot()的返回值为实数。
向量a和b的内积:
double dot(Vector a, Vector b) {
return a.x * b.x + a.y * b.y;
}
向量的外积
设向量a、b的夹角为θ,那么a、b的外积(cross product)|a × b| = |a||b|sinθ
两个向量a、b的外积是一个具有大小和方向的向量。外积的方向与a、b所在的平面垂直,且满足右手螺旋定则。以a为起始边向b旋转角度θ与b重合,此时伸出右手,四指握拳指向a的旋转方向,大拇指所指方向即为外积的方向。另外,外积的大小等于两个向量构成的平行四边形的面积。
用
a
=
(
a
x
,
a
y
,
a
z
)
,
b
=
(
b
x
,
b
y
,
b
z
)
a=(a_x,a_y,a_z),b=(b_x,b_y,b_z)
a=(ax,ay,az),b=(bx,by,bz)的形式表示两个向量,则a与b的外积为
∣
a
×
b
∣
=
(
a
y
×
b
z
−
a
z
×
b
y
,
a
z
×
b
x
−
a
x
×
b
z
,
a
x
×
b
y
−
a
y
×
b
x
)
|a×b|=(a_y×b_z-a_z×b_y,a_z×b_x-a_x×b_z,a_x×b_y-a_y×b_x)
∣a×b∣=(ay×bz−az×by,az×bx−ax×bz,ax×by−ay×bx)。(可通过矩阵导出)
将0代入z轴的值,则二维平面上两个向量a、b的外积大小为
∣
a
×
b
∣
=
∣
a
∣
∣
b
∣
s
i
n
θ
=
a
x
×
b
y
−
a
y
×
b
x
|a×b|=|a||b|sinθ=a_x×b_y-a_y×b_x
∣a×b∣=∣a∣∣b∣sinθ=ax×by−ay×bx
下面是一个求向量a、b外积的程序。外积cross()的返回值为表示向量大小的实数。
向量a与b的外积:
double cross(Vector a, Vector b) {
return a.x*b.y - a.y*b.x;
}