带数学方程解释的矩阵类






4.67/5 (15投票s)
2007 年 5 月 14 日
10分钟阅读

135719

3899
使用数学方程解释矩阵类的开发,包括基本算术函数和各种行列式方法
引言
互联网上有很多关于矩阵不同实现的资源。我的想法不仅仅是开发一个矩阵类,而是利用数学方程系统地处理这类编程问题。这个矩阵类远非完整,但附带了完整的文档。
矩阵在科学的各个领域都有许多重要的应用,并用于描述线性方程。它们包含线性变换的系数,可以以各种方式相加、相乘和分解。这使它们成为线性代数和矩阵理论中的关键概念。它们在科学和数学中如此重要,以至于它们是许多商业软件应用(如 MATLAB)的构建模块。本文将介绍使用 VB.NET 开发矩阵类。将对每个要编写的函数提供正式的数学定义。
背景
矩阵是数字的矩形数组。更一般地说,它可以是一个可以进行代数运算的量的二维数组。一般而言,这些“量”可以是任何可以相加或相乘的抽象实体——例如,整数、分数、多项式等——但在本文中,我们通过使用 Double
作为这些量来考虑一个简单的情况。
开发代码
矩阵A可以组织为
我们选择表示矩阵的标准在这里对我们来说并不重要。以下是一些信息
至于矩阵的标准表示,我们可以将单个元素 ai,j 标识为第 i 行第 j 列的元素。但是,如果我们考虑水平轴为 x,垂直轴为 y,那么根据上面的组织,ai,j 是位于 x=i 和 y=j 的元素,如果遵循行和列的约定,这将与 aj,i 是同一个元素(注意下标颠倒)。换句话说,x=i 表示列而不是行,但 x 穿过行(x=0 表示第 0 列,x=1 表示第 1 列,依此类推,但 x 穿过行)。数学和计算机语言中的二维空间都指定为第二种表示。因此,它被选用于我们的目的,我们可以通过声明我们的类来继续
Public Class Matrix
Implements ICloneable
Private M As Integer ' Horizontal Size = No. of Columns
Private N As Integer ' Vertical Size = No. of Rows
Public val(,) As Double
Private currentX As Integer
Private currentY As Integer
End Class
这里,val(,)
是一个包含矩阵元素的二维数组。在类的构造函数中,我们需要为我们的矩阵分配正确的维度
Public Sub New(ByVal X As Integer, ByVal Y As Integer)
SetDimensions(X, Y)
currentX = 0
currentY = 0
End Sub
Public Sub New(ByVal X As Integer)
SetDimensions(X, X)
currentX = 0
currentY = 0
End Sub
Private Function SetDimensions(ByVal X As Integer, ByVal Y As Integer)
M = X
N = Y
ReDim val(M - 1, N - 1)
End Function
构造函数已重载,一个用于矩形矩阵情况,另一个用于更具体的方阵情况。请注意,由于我们的二维数组是基于零索引的,如果 M 是水平计数,则元素索引为 0 到 M-1。
矩阵加法
现在,我们将向我们的类添加一个函数,该函数将接收另一个矩阵并执行逐元素加法。如果 C 是加到矩阵 A 以获得矩阵 B 的矩阵,则
对于所有 i 和 j。换句话说
和
同样,A 和 C 的维度必须匹配。
Public Function Add(ByVal C As Matrix) As Matrix
If M <> C.M Or N <> C.N Then
Throw New Exception("Matrices size Mismatch.")
End If
Dim B As Matrix = New Matrix(M, N)
For i As Integer = 0 To M - 1
For j As Integer = 0 To N - 1
B.val(i, j) = val(i, j) + C.val(i, j)
Next
Next
Return B
End Function
矩阵乘法
现在我们继续编写乘法函数。只有当第一个矩阵的列数 (M) 等于第二个矩阵的行数 (N) 时,才能对两个矩阵执行乘法。换句话说,如果矩阵 A 和 C 相乘,那么对于 B=AC ,根据我们的变量,只有当 A.M = C.N 时才可能。所以
Public Function Multiply(ByVal C As Matrix) As Matrix
If M <> C.N Then
Throw New Exception("Matrices size Mismatch.")
End If
...
...
End Function
记住矩阵乘法如下
所以,如果 B=AC,A 的一整行(例如,上面示例中的第 0 行是 [1 0 2])可以寻址为 A.val(i,J)
,其中 i 是访问行中单个元素的索引,j=J 将是常数(j=J=0 对于第 0 行的情况)。类似地,矩阵 C 的一整列指定为 C.val(I,j)
。
为了进行乘法,我们注意到运行索引 i 和 j 之间存在一对一的对应关系。因此,我们写 i=j=k。
结果矩阵 B 的第一个元素 B.val(0,0)
可以通过对 [A.val(k,0) 和 C.val(0,k)]
进行所有 k 的求和得到
一般来说,对于矩阵 B 中 (i, j) 的任何元素
其中
和
Dim B As Matrix = New Matrix(N, C.M)
For j As Integer = 0 To N - 1
For i As Integer = 0 To C.M - 1
For k As Integer = 0 To M - 1 ' or 0 to C.N -1
B.val(i, j) += val(k, j) * C.val(i, k)
Next
Next
Next
Return B
矩阵行列式
有许多方法可以获得矩阵的行列式。最流行的是按特定列或行展开。其他方法包括高斯-约旦消元法和高斯消元法。
按行/列展开
按特定行或列展开是最简单的方法,我们将首先用它来编程。对于所有忘记或不知道此方法的人,请考虑计算以下矩阵的行列式,按第 0 行展开
然后
其中 从原始矩阵 A 中删除第 0 行和第 0 列得到。注意 +1、-2 和 +3——符号在正负之间交替。从上面的步骤可以看出,计算这个 3x3 矩阵的行列式的问题被简化为计算三个 2x2 矩阵的行列式的问题。如果原始矩阵是 4x4 大小,那么按第 0 行展开后,将得到四个 3x3 矩阵。然后对于这四个中的每一个,重复相同的过程将得到三个 2x2 矩阵,最终总共得到二十四个 1x1 矩阵。对这些 2x2 矩阵继续相同的过程将得到二十四个 1x1 矩阵。
要对这个过程进行编程,首先考虑一个函数 SubMatrix(x, y)
,该函数从原始矩阵生成一个矩阵 S,其中删除第 y 行和第 x 列。这可以表示为
是将元素添加到矩阵中的运算符,就像求和
是算术加法的运算符一样。首先,我们编写一个过程来将元素
val(i,j)
添加到矩阵 S 中,并首先填充行。
Public Function AddElement(ByVal element As Double)
If currentX > (M - 1) Then
currentY += 1
currentX = 0
End If
Try
val(currentX, currentY) = element
Catch e As Exception
Throw New Exception("Matrix filled with values.")
End Try
currentX += 1
End Function
结果矩阵不应包含出现在第 y 行和第 x 列的矩阵 A 中的元素。例如,如果
那么 SubMatrix(2,1)
将得到
或
Public Function SubMatrix(ByVal x As Integer, ByVal y As Integer) As Matrix
Dim S As Matrix = New Matrix(M - 1, N - 1)
For j As Integer = 0 To N - 1
For i As Integer = 0 To M - 1
If (i <> x And j <> y) Then
S.AddElement(val(i, j))
End If
Next
Next
Return S
End Function
在定义了 SubMatrix(x,y)
之后,我们可以从上面的过程轻松地推导出递归关系
对于
和
每个递归过程都有一个基本情况。上面 val(0,0)
的基本情况利用了一个事实,即 1x1 矩阵中唯一的元素是该矩阵的行列式。
Public Function Determinant() As Double
If M = 1 And N = 1 Then
Return val(0, 0)
End If
Dim temp As Double
Dim MySubMatrix As Matrix
Dim j As Integer = 0
For i As Integer = 0 To M - 1
MySubMatrix = SubMatrix(i, j)
temp += ((-1) ^ (i + j) * val(i, j) * MySubMatrix.Determinant())
Next
Return temp
End Function
对于所有希望将其与理论和标准定义(如子式和代数余子式)联系起来的人,请注意:det(SubMatrix(i,j))
是矩阵 A 的子式 M(i,j),而 A 的代数余子式 C(i,j) 定义为 (−1)i + j 乘以 A 的子式 M(i,j)。然后可以很容易地看出,上面的方程是拉普拉斯公式沿第 j 行的一个特定形式,当 j = 0 时。
高斯消元法
这种方法不利于良好的编程,因为它具有阶乘阶数,即 O(n!)。因此,它的效用对于较大的 n 值大大降低,因为它需要很长时间才能执行。为了避免这种情况,我们利用了矩阵的一些性质。其中一个在这里非常重要,它说明
将矩阵的一整行或一整列乘以一个常数,然后将由此产生的列从另一行或另一列中减去,不会改变矩阵的行列式。
通过对矩阵进行适当的操作,我们可以使任何行中的除一个元素外的所有元素都等于零。在这种情况下,设 j=Y 为一个常数行。从这里,我们需要展开矩阵。让我们将 k 定义为 Y 行中的第一个非零元素,其中我们必须让 Y = 0
Dim Y As Integer = 0
Dim k As Integer
For i As Integer = 0 To M - 1
If val(i, Y) <> 0 Then
k = i
Exit For
End If
Next
现在我们取这个非零元素 val(k,Y)
,并尝试使 Y 行中的所有其他元素都等于零。这可以通过利用一个属性来实现,并且最好通过一个例子来理解。设
在这里,取 Y = 0。那么 k = 1,因为元素 val(1,0) = 2
是 Y = 0 行中的第一个非零元素。现在我们分析这一整行。我们希望这一行中的所有元素(除一个外)都为零。因此,通过使用中间列,我们可以使所有其他列的顶端元素等于零(在这种情况下是元素 3)。换句话说,需要将列 i = k + 1 及之后的列置零,因为对于 j < k,元素已经为零。通过利用该属性,我们将以 2 为头的列乘以 (2/3)。这将使矩阵变为
注意上面的不等式。现在根据该属性,可以从最后一列减去中间列以恢复 A 。因此
请注意,中间列未更改;它保持与原始列相同。按第 0 行展开得到
这里,由于第一行中除一个元素外所有其他元素都为零,一个 3x3 矩阵只产生了一个 2x2 矩阵。这与前一种方法相比是一个很大的改进,在前一种方法中,一个 nxn 矩阵产生 n 个 (n-1xn-1) 矩阵。相反,这种方法总是从一个 nxn 矩阵产生一个 (n-1xn-1) 矩阵。
为了用数学方式(从而“以编程方式”)表达这一完整过程,我们写
对于所有 和
当然,如果一列的顶端元素已经是零,我们就不浪费时间再次将其置零。所以检查 val(i,Y) = 0
。
Dim f As Double
For i As Integer = k + 1 To M - 1
If val(i, Y) <> 0 Then
f = val(i, Y) / val(k, Y)
For j As Integer = 0 To N - 1
NewMatrix.val(i, j) = val(i, j) - val(k, j) * f
Next
End If
Next
这里,
是一个声明为 double 的因子。我们在上面的代码中写了 NewMatrix.val(i,j) = . . .
,而不是 val(i,j) = . . .
。这是一个小的内存节省步骤,解释如下。
在粗略的按行展开方法中,我们从特定行中取出一个元素,生成 SubMatrix
,然后我们对新形成的矩阵执行相同的操作。在通过 SubMatrix
形成新矩阵后,我们没有丢弃旧矩阵,因为该旧矩阵中还有其他元素等待处理。然而,在此方法中,一旦形成 SubMatrix
,我们就可以轻松丢弃旧矩阵,因为没有等待的元素。这是因为它们都为零。但是,重要的是我们不要丢弃原始矩阵,因为行列式方法不打算更改调用它的矩阵的内容。它可以随意处理它自己创建的矩阵或子矩阵,因此该方法的签名是
Private Function GEDeterminant(ByVal DoClone As Boolean) As Double
If M = 1 And N = 1 Then
Return val(0, 0)
End If
Dim NewMatrix As Matrix
If DoClone Then
NewMatrix = Clone()
Else
NewMatrix = Me
End If
...
End Function
现在我们使用
NewMatrix = NewMatrix.SubMatrix(k, Y) 'Save space
temp += ((-1) ^ (k + Y)) * val(k, Y) * NewMatrix.GEDeterminant(False)
Return temp
其中 temp
之前已通过以下方式声明为临时变量
Dim temp As Double
Clone()
可以轻松编程为
Public Overridable Function Clone() As Object Implements ICloneable.Clone
Dim temp As Matrix = New Matrix(M, N)
temp.val = val.Clone
Return temp
End Function
Public
函数的形式如下,因为我们需要 Clone()
原始矩阵
Public Function GEDeterminant() As Double
If IsSquare() Then
Return GEDeterminant(True)
Else
Throw New Exception("Determinant exists only possible"
& _ "for a sqaure matrix.")
End If
End Function
最后
Public Overrides Function ToString() As String
Dim temp As String
For Y As Integer = 0 To N - 1
For X As Integer = 0 To M - 1
temp &= val(X, Y) & ","
Next X
temp &= Chr(13)
Next Y
Return temp
End Function
关注点
有趣的是,在编程 GEDeterminant()
方法时,我不知道它被称为高斯消元法。按行展开根本不实用。例如,一个 10x10 矩阵的 O(n!) 行列式会导致几十秒的延迟,而对于高斯消元法 O(n3) 的一千个 10x10 矩阵,延迟仅为 200 毫秒。
这个矩阵类帮助我解决了许多不同领域的问题。使用克莱姆法则求解联立线性方程组是矩阵的一个重要用途。请记住,我在文章前面使用了“可以相加或相乘的抽象实体”这个短语。对于本文,我们使用了 Double 作为抽象实体,但使用自定义的 Polynomial 可以帮助我们解决复杂的电气电路——就像 SPICE 所做的那样——使用修改后的节点分析。矩阵的另一个重要应用是我广泛使用的,即从任意数量的给定样本中逼近多项式函数。逼近通常可以是任何形式,包括指数、正弦等。正弦形式的逼近导致了傅里叶级数——包括连续和离散——的形成,从而开辟了一个全新的探索世界。
历史
- 2007 年 5 月 14 日 - 发布原版本