程序员数学:用Python学透线性代数和微积分
上QQ阅读APP看书,第一时间看更新

2.2 平面向量运算

和数一样,向量也有自己的运算方式。对向量进行运算可以生成新的向量,而且可以将结果可视化。向量运算除了包含代数变换,还有几何变换。我们从最基本的运算开始:向量加法

向量加法很简单:给定两个输入向量,将它们的坐标相加,得到新的坐标,然后将它们的坐标相加,得到新的坐标。用这些新的坐标创建一个新的向量,就得到了原始向量的向量和。举个例子,因为4 + (-1) = 3以及3 + 1 = 4,所以(4, 3) + (-1, 1) = (3, 4)。用Python实现向量加法非常简单,如下所示。

def add(v1,v2):
    return (v1[0] + v2[0], v1[1] + v2[1])

因为可以用平面上的箭头或者点来表示向量,所以能用这两种方式直观地看到加法的结果(见图2-13)。从原点(0, 0)开始,向左移动1个单位再向上移动1个单位,就可以到达点(-1, 1)。转换一下起点,要想到达(4, 3) + (-1, 1)所表示的点,则要从(4, 3)开始,向左移动1个单位,再向上移动1个单位。也可以将这种方式描述为逐一沿着箭头移动。

图2-13 绘制出(4, 3)和(-1, 1)的向量和

箭头形式的向量加法有时被称为首尾加法。这是因为如果把第二个箭头的尾部移到第一个箭头的头部(不改变其长度或方向),最终的向量和就是一个从第一个箭头起点到第二个箭头终点的箭头(见图2-14)。

图2-14 向量的首尾加法

当我们提到箭头这种向量的表示方式时,其实际含义是“特定方向上的一段特定距离”。如果你在一个方向上移动一段距离,在另一个方向上又移动了一段距离,那么向量和可以表示移动的整体距离和方向(见图2-15)。

图2-15 向量和表示在平面上移动的整体距离和方向

添加一个向量意味着移动或平移一个现有的点或点的集合。如果将向量(-1.5, -2.5)和dino_vectors中的每一个向量相加,就会得到一个新的向量列表,其中的每个向量都相对于原始向量左移1.5个单位、下移2.5个单位。代码如下所示。

dino_vectors2 = [add((-1.5,-2.5), v) for v in dino_vectors]

结果就是,同一个恐龙形状被向量(-1.5, -2.5)向左下方进行了移动。为了更好地说明,我们用Polygon将两只恐龙画出来(见图2-16)。

draw(
    Points(*dino_vectors, color=blue),
    Polygon(*dino_vectors, color=blue),
    Points(*dino_vectors2, color=red),
    Polygon(*dino_vectors2, color=red)
)

图2-16 原始恐龙(蓝色,书中为深灰色)和平移后的副本(红色,书中为浅灰色)。将每一个点和向量(-1.5, -2.5)相加,让恐龙向左下方移动

右图中的那些箭头显示每个点通过和向量(-1.5, -2.5)相加,移动了相同的方向和距离。想象一下,如果恐龙是二维计算机游戏中的一个角色,这样的平移变换就会派上用场。用户按下方向键,恐龙就可以在屏幕上向着相应的方向移动。我们将在第7章和第9章中对相关内容做具体介绍。

2.2.1 向量的分量和长度

把一个已有的向量分解成更小的多个向量之和是一种非常有用的操作。举个例子,如果你在纽约问路,听到“往东走4个街区,再往北走3个街区”比“往东北走800米”更有意义。同样,把某个向量看作指向方向的向量与指向方向的向量之和也更有意义。

例如,图2-17显示了将向量(4, 3)改写为(4, 0)与(0, 3)之和。如果把向量(4, 3)看作平面上的导航路径,(4, 0)与(0, 3)之和就可以让我们顺次通过两条路径,抵达一个点。

图2-17 将向量(4, 3)分解为(4, 0)与(0, 3)之和

(4, 0)和(0, 3)这两个向量分别称为分量分量。有时候你无法沿着对角线行进(纽约市的建筑会阻挡你前进的步伐),那么向右走4个单位、再向上走3个单位,才能到达同一个点,一共走了7个单位。

向量的长度(length)就是代表它的箭头的长度,等价于从原点到它表示的点的距离。在纽约市,乌鸦可以做到沿着箭头飞行(不会被建筑物阻隔)。方向上的向量长度一目了然,就是相应坐标轴上的刻度数:虽然方向不同,但(4, 0)和(0, 4)都是长度为4的向量。因为向量(4, 3)其实在(4, 0)和(0, 3)所形成平行四边形的对角线上,所以还要花点儿功夫来计算其长度。

你应该想起了一个公式:勾股定理。该定理描述:对于一个直角三角形(两边夹角为90°的三角形),最长边长度的平方是其余两条边长度的平方之和。最长边叫作直角三角形的斜边,长度用表示,其计算公式为,其中代表另外两条边的长度。如果就是的平方根(见图2-18)。

图2-18 利用勾股定理,根据分量和分量的长度求出向量的长度

将一个向量分解成两个分量是很容易的,因为总能找到一个对应的直角三角形。如果知道各分量的长度,就可以计算出斜边的长度,也就是向量的长度。向量(4, 3)等于(4, 0) + (0, 3),是两个边长分别为4和3、相互垂直的向量之和。向量(4, 3)的长度是42 + 32的平方根,即25的平方根,也就是5。在每个街区都是正方形的城市里,向东行驶4个街区、再向北行驶3个街区,就相当于向东北行驶5个街区。

整数长度是一种特殊情况,使用勾股定理得出的长度通常不是整数。(-3, 7)的长度可以通过用它的分量长度3和7来计算。

可以将这个公式转化为Python中的length函数,它接收一个二维向量并返回其浮点数形式的长度。

from math import sqrt
def length(v):
    return sqrt(v[0]**2 + v[1]**2)

2.2.2 向量与数相乘

向量的重复相加很简单,只要把箭头一直不断地首尾连接起来即可。如果向量的坐标是(2, 1),那么对5个这样的向量求和,就是,如图2-19所示。

图2-19 将向量重复相加

如果是一个数,就不用写出像这么冗余的表达式,直接用表示即可。向量当然也可以这样写。将相加5次的结果是一个方向相同但长度为其5倍的向量。如此一来,我们就可以用任意整数或小数与一个向量相乘。

将向量乘以数的运算称为标量乘法。处理向量时,普通的数通常被称为标量(scalar)。scalar这个名字非常贴切,因为运算的效果是将目标向量按给定的系数进行缩放(scale)。标量是否是整数并不重要,我们可以很容易地画出一个长度是另一个向量2.5倍的向量(见图2-20)。

图2-20 向量乘以2.5的标量乘法

对应到向量分量上,每个分量都按相同的系数进行缩放。可以把标量乘法想象成改变了一个由向量及其分量所定义的直角三角形的大小,但不影响其长宽比。图2-21将向量和它的标量乘积叠加在一起显示,无论是向量自身的长度还是其分量的长度,都是原先的1.5倍。

图2-21 标量乘法就是将向量的两个分量按同一系数缩放

在坐标系中,用标量1.5和相乘,可以得到一个新的向量(9, 6),其中每个分量都是其原始值的1.5倍。从计算方面讲,我们通过将向量的每个坐标乘以标量来执行向量的标量乘法。再举一个例子,将向量乘以系数6.5可以写作:

这里测试了可以使用分数作为标量,还可以使用负数。如果原始向量是(6, 4),那么该向量的-1/2倍是多少?答案是(-3, -2)。图2-22显示,这个向量的长度是原向量的一半,而且指向相反的方向。

图2-22 用-1/2乘以向量的标量乘法

2.2.3 减法、位移和距离

向量的标量乘法和数的乘法一致:一个数的整数倍数与将这个数重复相加是一样的。这同样适用于向量,负向量和向量减法也如出一辙。

给定一个向量,其向量与标量乘积相同。若是(-4, 3),那么它的负值就是(4, -3),如图2-23所示。我们通过将每个坐标乘以-1(或者说,改变每个坐标的符号)来得到负向量。

图2-23 向量及其负向量

在一条线上,从零开始只有两个方向:正和负。然而在平面上,有很多(实际上是无限多)方向,所以不能说中一个是正的、另一个是负的。可以说,对于任一向量,负向量具有相同的长度,但指向相反的方向。

有了负向量的概念,就可以定义向量减法了。对于数,相同。我们给向量设定同样的约定。要想从向量减去一个向量,需要把向量加到上。把向量看作两个点,就是相对于的位置。如果把看作从原点开始的箭头,由图2-24可知,是从头部到头部的箭头。

图2-24 的结果是从头部到头部的箭头

的坐标是的坐标差。在图2-24中,。因此,的坐标为(-1-2, 3-2) = (-3, 1)。

再来看看向量的区别。可以使用draw函数来绘制这两个点,并在它们之间画一条线段。代码如下所示。

draw(
    Points((2,2), (-1,3)),
    Segment((2,2), (-1,3), color=red)
)

表示从点开始,需要向左走3个单位、再向上走1个单位,才能到达点。这个向量有时被称为从位移(displacement)。图2-25中从的直线段是由上面的Python代码绘制出的,显示了两点之间的距离

图2-25 平面内两点之间的距离

该线段的长度用勾股定理计算,如下所示。

位移是一个向量,而距离是一个标量(一个数)。距离不足以说明如何从到达,因为有很多点到的距离相同。图2-26显示了其他几个距离相同的整数坐标点。

图2-26 距等距离的几个点

2.2.4 练习

练习2.6:对于向量、向量和向量的结果是什么?的结果又是什么?

:对于向量、向量和向量,结果如下所示。

 

练习2.7(小项目):通过将所有向量各自的坐标和坐标相加,可以实现任意数量的向量相加。例如,向量和(1, 2) + (2, 4) + (3, 6) + (4, 8)有分量1 + 2 + 3 + 4 = 10与分量2 + 4 + 6 + 8 = 20,结果为(10, 20)。实现新的add函数,接收任意多个向量作为参数。

def add(*vectors):
    return (sum([v[0] for v  in vectors]), sum([v[1] for v  in vectors]))

 

练习2.8:实现函数translate(translation, vectors),接收一个平移向量和一个向量列表,返回一个根据平移向量平移后的向量列表。例如,对于translate ((1,1), [(0,0), (0,1,), (-3,-3)]) ,它应该返回[(1,1), (1,2), (-2, -2)]

def translate(translation, vectors):
    return [add(translation, v) for v  in vectors]

 

练习2.9(小项目):向量之和结果相同。用坐标形式的向量和的定义来解释其原因。同时,用图像来说明为什么这在几何上是成立的。

:如果把两个向量相加,其中坐标都是实数,那么向量的结果是,而的结果是。这两对坐标相同,因为实数相加时的顺序并不重要。对于首尾加法,无论哪种顺序都能得到相同的向量和。为了更形象地解释这一点,图2-27展示了将一对向量首尾相加的示例。

图2-27 任意顺序的首尾相加都会得到相同的向量

不管是还是(虚线),得到的向量都是一样的(实线)。在几何学中,两组形成了一个平行四边形,而向量和就是对角线。

 

练习2.10:在如图2-28所示的三个箭头向量(标为)中,哪一对的和对应的箭头最长?哪一对的和对应的箭头最短

图2-28 哪一对的和对应的箭头最长,哪一对的和对应的箭头最短

:可以通过首尾加法测量每一对向量和,如图2-29所示。

图2-29 将题目中的向量两两首尾相加

检查结果,可以看到最短(的方向几乎相反,接近于互相抵消),最长的是

 

练习2.11(小项目):实现一个处理向量加法的Python函数,显示100个相互不重叠的恐龙图像。这体现了计算机图形学的威力。想象一下,手绘2100个坐标对是一件多么乏味的事情!

:可以在垂直和水平方向上平移恐龙,设置合适的间距,使它们不重叠。这里省去网格线、坐标轴、原点和坐标点,让图像更清晰一些。代码如下所示。

def hundred_dinos():
    translations = [(12*x,10*y)
                    for x in range(-5,5)
                    for y in range(-5,5)]
    dinos = [Polygon(*translate(t, dino_vectors),color=blue)
                for t in translations]
    draw(*dinos, grid=None, axes=None, origin=None)

hundred_dinos()

结果如图2-30所示。

图2-30 逃命吧!100只恐龙出现

 

练习2.12:对于(3, -2) + (1, 1) + (-2, -2),是分量还是分量更长?

:向量和(3, -2) + (1, 1) + (-2, -2)的结果是(2, -3),其中分量为(2, 0),分量为(0, -3)。分量的长度为2个单位(向右),而分量的长度为3个单位(向下,因为它是负数)。所以分量更长。

 

练习2.13:向量(-6, -6)和(5, -12)的分量和长度分别是多少?

:(-6, -6)的分量是(-6, 0)和(0, -6),长度都是6。(-6, -6)的长度是62 + 62的平方根,大约是8.485。

(5, -12)的分量是(5, 0)和(0, -12),长度分别为5和12。(5, -12)的长度是52 + 122 = 25 + 144 = 169的平方根,即13。

 

练习2.14:假设有一个长为6的向量和它的分量(1, 0)。的坐标可能是什么?

:因为(1, 0)的长度为6,其分量的长度为1,所以分量的长度必须满足,即。那么分量的长度约为5.916。但无法确定分量的方向。向量可能是(1, 5.916)或(1, -5.916)。

 

练习2.15dino_vectors列表中哪个向量的长度最长?用我们实现的length函数快速计算出答案。

>>> max(dino_vectors, key=length)
(6, 4)

 

练习2.16:假设向量的坐标是。那么的坐标近似值是多少?画出原向量和新向量。

的近似值如下。

将每个坐标按照倍进行放大,可以得到如下值。

放大后的向量比原来的长,如图2-31所示。

图2-31 原始向量(短)和放大后的向量(长)

 

练习2.17:写一个Python函数scale(s,v),将输入向量和输入标量相乘。

def scale(scalar,v):
            return (scalar * v[0], scalar * v[1])

 

练习2.18(小项目):用代数方法证明,将坐标按照一个系数缩放,会将向量的长度以同等系数缩放。假设一个长度为的向量坐标为。证明,对于任意非负实数的长度是。(不能是负值,因为向量的长度不可能为负。)

:用符号来表示向量的长度。从题目可得到如下公式。

从而算出的长度。

如果不是负值,那么,缩放以后向量的长度就是

 

练习2.19(小项目):假定,而是实数,并且假设。在平面上,向量可能的终点是哪里?

请注意,向量的运算顺序和数的运算顺序一致。我们假设先进行标量乘法,然后进行向量加法(除非有括号)。

:若为0,可能的值位于线段(-1, -1)到(1, 1)上。若不为0,则在方向(-1, 1)或-(-1, 1)上离开该线段最多3个单位。结果所在的区域是平行四边形,顶点分别为(2, 4)、(4, 2)、(2, -4)和(4, -2)。可以用多个随机的进行测试和验证。

from random import uniform
u = (-1,1)
v = (1,1)
def random_r():
    return uniform(-3,3)
def random_s():
    return uniform(-1,1)

possibilities = [add(scale(random_r(), u), scale(random_s(), v))
                 for i in range(0,500)]
draw(
    Points(*possibilities)
)

运行这段代码,会得到图2-32,显示了给定约束条件下可能的终点。

图2-32 在给定约束条件下可能出现的位置

 

练习2.20:用代数法证明为什么一个向量和其负向量具有相同的长度。

提示:将向量坐标及其负向量坐标代入勾股定理的公式。

的负向量的坐标为,但并不影响长度(二者长度相等)。

 

练习2.21:在如图2-33所示的七个用箭头表示的向量中,哪两个是一对相反的向量?

图 2-33

:向量是一对相反的向量。

 

练习2.22:假定是任意二维向量。的坐标是什么?

:二维向量的坐标为,其负向量的坐标为,因此可以得到如下等式。

答案是(0, 0)。在几何意义上,这意味着如果你沿着一个向量走到头、再折返回来,最终还是会回到原点(0, 0)。

 

练习 2.23:对于向量的结果分别是什么?

:由,可以得到如下等式。

 

练习2.24:实现Python函数subtract(v1,v2),返回v1 - v2的结果。该函数接收两个二维向量作为输入,返回一个二维向量作为输出。

def subtract(v1,v2):
    return (v1[0] - v2[0], v1[1] - v2[1])

 

练习2.25:实现Python函数distance(v1,v2),返回两个输入向量之间的距离。(注意:上一个练习中的subtract函数已经返回了两个向量之间的位移。)

实现另一个Python函数perimeter(vectors),它接收一个向量列表作为参数,并返回每个向量到下一个向量的距离之和(包含末位向量与首位向量之间的距离),以此来获取向量集合dino_vectors所定义的恐龙的周长。

:距离就是两个输入向量之差的长度。

def distance(v1,v2):
    return length(subtract(v1,v2))

要算出恐龙的周长,需要将列表中每一对相邻向量的距离以及首末位向量之间的距离相加。

def perimeter(vectors):
    distances = [distance(vectors[i], vectors[(i+1)%len(vectors)])
                 for i in range(0,len(vectors))]
    return sum(distances)

先用边长为1的正方形进行测试。

>>> perimeter([(1,0),(1,1),(0,1),(0,0)])
4.0

然后可以算出恐龙的周长。

>>> perimeter(dino_vectors)
44.77115093694563

 

练习2.26(小项目):令为向量(1, -1)。假定有另一个正整数坐标为)的向量,且它与的距离为13,那么从的位移是多少?

提示:可以使用Python,通过穷举的方式搜索向量

:我们只需要搜索可能的整数对 ,其中在1的前后13个单位内,在-1的前后13个单位内。

for n in range(-12,15):
    for m in range(-14, 13):
        if distance((n,m), (1,-1)) == 13 and n > m > 0:
            print((n,m))

只找到了一个结果:(13, 4)。它相对于(1, -1)右移了12个单位、上移了5个单位,所以位移是(12, 5)。

仅仅有长度还不足以描述向量,两个向量之间的距离也不足以给出从一个向量得到另一个向量的完整信息。在这两种情况下,缺少的信息都是方向。如果你知道一个向量的长度,以及它指向的方向,就可以找到它对应的坐标。这就是三角学的内容,我们将在下一节中回顾这些知识。