點乘和叉乘及其物理意義(C++STL實現)

Inside_Zhang發表於2015-11-14
  1. 一些錯誤觀念的澄清,比如數學意義上的點積叉積並不對應matlab程式中的.*(按位相乘)和*(矩陣乘法)

  2. 內積的物理意義

    • 一種向量到標量的對映
    • 兩向量的夾角的計算
    • 兩向量是否正交的判斷
    • 兩向量的相似性(similarity)的度量
  3. 叉積的意義

  4. 如何使用C++語言(STL容器,運算子過載):

    • 表示向量
    • 計算內積
    • 計算叉積
    • 計算模長
    • 計算兩向量的夾角
    • 計算點到直線的距離

prerequisites

內積(inner product)

又叫點乘,點積(dot product),數量積,顧名思義得到的是一個標量(scalar)。

  • 代數定義

向量x=[x1,x2,,xn]

x=[x_1, x_2, \ldots, x_n]
y=[y1,y2,,yn]
y=[y_1, y_2, \ldots, y_n]
的內積定義為:

xy=inxiyi=x1y1+x2y2++xnyn
x\cdot y=\sum_i^nx_iy_i=x_1y_1+x_2y_2+\dots+x_ny_n

也即:內積等於向量的對應位相乘再相加,如果從函式的觀點來看的話,即是兩個向量相互作用得到一個標量。

內積對相互作用的兩個向量x,y

x,y
的長度也即各自所含元素的個數是沒有限制的。這點不同於向量叉積,叉積所要的向量長度最高為3.

  • 幾何定義

歐式空間中,向量是一個同時擁有長度和方向的幾何物件。向量x

x
的長度記為x
\|x\|
,兩個向量的內積定義為:

xy=xycosθ
x\cdot y=\|x\|\|y\|cos\theta

θ

\theta
標識著兩向量的夾角。可見當向量正交時,θ=90
\theta = 90^\circ
xy=0
x\cdot y = 0

當向量y
y
被歸一化為長度為1的單位向量時,xy=xcosθ
x\cdot y=\|x\|cos\theta
,我們來考察x,y
x,y
均為二維的情形:


這裡寫圖片描述

如上圖所示,此時二者的內積表示的恰是其中一個向另外一個的投影,投影長度越小,說明二者的夾角越大,反之亦然。當兩向量同時歸一化為1時,此時內積的定義為:xy=cosθ

x\cdot y = cos\theta
,內積越大,說明兩者夾角越小,也間接地說明兩者也就越相似,故在許多機器學習的演算法,常用餘弦相似性來度量兩特徵向量的逼近程度。內積既然能夠表徵兩向量的夾角,自然判斷兩向量是否正交(值為0)就更不在話下了。

叉積

又叫叉乘(cross product)或者外積,它的計算結果是一個向量而非標量。叉積所在的向量與參與運算的兩個向量都正交,也即正交於原來的兩個向兩邊所決定的平面,也即兩向量所決定的平面的法向量可通過計算叉積的方式得以確定。當參與運算的兩向量是平行的兩個向量時,得到的叉積為0,也即可通過計算叉積的方式判斷兩向量是否平行。

代數定義

  • 二維時

x×y=x1y2x2y1
x\times y = x_1y_2-x_2y_1
  • 三維時


這裡寫圖片描述

根據如圖的計算方法可得:

u×v====iu1v1ju2v2ku3v3(u2v3i+u3v1j+u1v2k)(u3v2i+u1v3j+u2v1k)(u2v3u3v2)i+(u3v1u1v3)j+(u1v2u2v1)k(u2v3u3v2,u3v1u1v3,u1v2u2v1)
\begin{eqnarray} \begin{split} \textbf{u}\times \textbf{v}=& \begin{vmatrix} \textbf {i} & \textbf{j} & \textbf{k} \\ u_1 & u_2 & u_3 \\ v_1 & v_2 & v_3 \end{vmatrix}\\ =&(u_2v_3\textbf{i}+u_3v_1\textbf{j}+u_1v_2\textbf{k})-(u_3v_2\textbf{i}+u_1v_3\textbf{j}+u_2v_1\textbf{k})\\ =&(u_2v_3-u_3v_2)\textbf{i}+(u_3v_1-u_1v_3)\textbf{j}+(u_1v_2-u_2v_1)\textbf{k}\\ =&(u_2v_3-u_3v_2, u_3v_1-u_1v_3, u_1v_2-u_2v_1) \end{split} \end{eqnarray}

幾何定義

x×y=xysinθn⃗ 
x\times y = \|x\|\|y\|sin\theta \vec{n}

n⃗ 

\vec{n}
表示叉積方向上的單位向量。

中學知識告訴我們三角形的面積計算公式為:

S=xysinθ2=zh2
S=\dfrac{\|x\|\|y\|sin\theta}{2}=\dfrac{\|z\|h}{2}

其中θ
\theta
表示的是x,y
x, y
之間的夾角,由以上兩個公式我們可得到三角形的高或者點到其所對的邊的距離,也即點到直線的距離,的計算公式:
h=x×y/z
h=\|x\times y\|/\|z\|

C++ STL實現

  • 向量的定義:
typedef vector<double> Vec;
  • 矩陣減法
Vec operator-(const Vec& x, const Vec& y)
{
    assert(x.size() == y.size());
                        // #include <cassert>
    Vec tmp;
    for(size_t i = 0; i < x.size(); ++i)
        tmp.push_back(x[i] - y[i]);
    return tmp;         // 返回區域性變數的拷貝
}
  • 內積和叉積的定義

為了形式的簡單,我們在C++中以*表示內積,以^表示叉積,分別對二者進行運算子過載:

double operator*(const Vec& x, const Vec& y)
{
    assert(x.size() == y.size());       // #include <cassert>
    double sum = 0.;
    for (size_t i = 0; i < x.size(); ++i)
        sum += x[i]*y[i];
    return sum;
}

// 三維的情況
Vec operator^(const Vec& x, const Vec& y)
{
    assert(x.size() == y.size() && x.size() == 3);
    return Vec{x[1]*y[2]-x[2]*y[1], 
                x[2]*y[0]-x[0]*y[2],
                x[0]*y[1]-x[1]*y[0]};
            // uniform initialization, C++11新特性
}

// 二維就姑且返回其模長吧
double twoDCrossProd(const Vec& x, const Vec& y)
{
    return x[0]*y[1]-x[1]*y[0];
}
  • 模長或者範數的計算
double norm(const Vec& x)
{
    double val = 0.;
    for(auto elem: x)
        val += elem*elem;
    return sqrt(val);   
                    // #include <cmath>
}
  • 向量夾角的計算
#define PI 3.14159265358979323846
// 弧長向弧度的轉換
double toDegree(double val)
{
    return val*180/PI;
}

double angle(const Vec& x, const Vec& y)
{
    return toDegree(acos(x*y/norm(x)/norm(y)));
            // x*y, 計算二者的內積
}
  • 點到直線的距離
// x0, x1, x2 分別表示三角形的三個頂點的座標
// 這裡計算的是點x0到x1和x2構成的直線的距離
double distance(const Vec& x0, const Vec& x1, const Vec& x2)
{
    return twoDCrossProd(x1-x0, x2-x0)/norm(x1-x2);
}

客戶端程式:

int main(int, char**)
{
    Vec x{1, 0, 0}, y{0, 1, 0};
    Vec z = x^y;        // 計算叉乘
    copy(z.begin(), z.end(), ostream_iterator<double>(cout, " "));
    cout << endl;
        // 0 0 1

    Vec alpha{1, 0}, beta{1, 1};
    cout << angle(alpha, beta) << endl;
            // 45

    Vec x0{0, 0}, x1{1, 0}, x2{0, 1};   
    cout << distance(x0, x1, x2) << endl;
            // 1/sqrt(2)
    return 0;
}

相關文章