矩阵及其基本算法
矩阵及其基本算法
计13 刘汝佳
矩阵及其基本算法
矩阵的表示 矩阵的基本运算 小结和应用举例
一、矩阵的表示
矩阵在形式上最直接的表示是一个二维数组,但 是在一些特殊的场合中,我们需要引入一些特殊 的方法来表示一些特殊的矩阵。在本节中,大家 还将了解到以下几种表示方法: 还将了解到以下几种表示方法:
三角矩阵的压缩表示法 稀疏矩阵的三元组表示法 稀疏矩阵的十字链表表示法
稀疏矩阵的十字链表表示(3) 稀疏矩阵的十字链表表示
结点类型如下定义:
struct Tnode { int row, col; int value; Tnode *left, *right, *up, *down; }; row,col分别为该非零元素的位置,value为它的值。 row,col分别为该非零元素的位置,value为它的值。 left,right,up,down分别为指向四个方向的后继元素。 left,right,up,down分别为指向四个方向的后继元素。
稀疏矩阵的十字链表表示(2) 稀疏矩阵的十字链表表示
为了加强同一行和同一列之间元素的联系,我 们把每一行分别做成一个链表,把每一列也分 别做成一个链表。 通过对链表的遍历,我们可以很方便的按顺序 访问到某一特定行或列的所有元素。插入和删 除操作也很方便。 这样,我们了建立一种十字型的链表结构,每 个结点有上,下,左,右四个指针和自身的位 置坐标,大小共7 置坐标,大小共7个域。
第三种情况和三元组表示下的加法很类似,只 是下标pA,pB换成了指针。 是下标pA,pB换成了指针。 同样的,需要注意两个非零元素之和可能等于 零,从而不能插入到结果中
矩阵的转置(1) 矩阵的转置
矩阵的转置
在二维数组中,转置可以通过简单的坐标变换得到 在三元组表示法中,在对每个元素进行坐标变换后 还需要进行一次排序,以维持元素位置的有序性。 在十字链表表示法中,我们不仅需要对每个结点进 行坐标变换,还需要交换行链表和列链表,以及更 改每个结点四个方向的指针,实现比较麻烦。
三角矩阵的压缩表示(2) 三角矩阵的压缩表示
我们从上到下,从左到右地储存各个元 素,如下图:
a11 a12 a13 a14 a a a 22 23 24 a33 a34 a44
i −1 l =1
a1 a2 a3 a4 a a a 5 6 7 a8 a9 a10
矩阵乘法(1) 矩阵乘法
矩阵乘法
在二维数组表示中,最简单的方法就是对每个元素用循环累 加出结果,二重循环枚举每个元素,共三层循环。 在三元组表示中,对于A中的一个元素A 我们只需要访问B 在三元组表示中,对于A中的一个元素Aij,我们只需要访问B 中第j行所有元素B 中第j行所有元素Bjk,把每个乘积Aij ×Bjk加到Cik中。这样, 把每个乘积A 加到C 每遍历A的一行元素,就计算出了C 每遍历A的一行元素,就计算出了C的一行元素。和快速转置 算法类似,我们可以事先算出B 算法类似,我们可以事先算出B的每行元素的下标范围,再用 一个循环依次考虑A 一个循环依次考虑A中的每个元素就可以了。 十字链表在本质上是三元组的链式存储,所以乘法和三元组 差别不大,但是实现却麻烦得多。该方法的好处在于,把中 间结果A 间结果Aij ×Bjk 加到Cik时,如果Cik并不存在,就需要对矩阵 加到C 时,如果C 进行插入操作。在十字链表上的插入操作比三元组快得多。
矩阵表示方法小结
矩阵的表示方法和应用是分不开的。 我们衡量一种表示方法的优劣,需要从不同的 角度进行分析。 适用范围 空间需求量 基本操作的时间消耗 实现的难易程度 以上几种方法都在某些方面表现良好而其他方 面不够理想,因此我们需要根据实际需要的侧 重点不同,选择合适的表示方法
二、矩阵的基本运算以通过下列代码来实现: void MatrixT(TMatrix A) { TMatrix B; B.m=A.n; B.n=A.m; for(int i=1;i<=A.n;i++) for(int j=1;j<=A.m;j++) B.numbers[j][i]=A.numbers[i][j]; return B; }
对代码稍作修改,我们可以对矩阵进行旋转和镜像变换生成8 对代码稍作修改,我们可以对矩阵进行旋转和镜像变换生成8个新矩阵
矩阵的转置(3) 矩阵的转置
前面提到过,三元组表示法中,矩阵的转置可 以通过坐标变换后排序来实现。 不过,我们通常使用的是另外一种方法。这种 方法不用排序,而速度也更快。 快速转置算法:直接写到正确的位置。
矩阵的线性运算
矩阵的数乘: 矩阵的数乘: B=kA
在任何一种表示法中,我们都可以通过遍历所有 元素的方法完成数乘运算。
矩阵的加法: 矩阵的加法: C=A+B
在二维数组表示法中,我们可以通过二重循环来 进行矩阵加法 在稀疏矩阵中,注意到结果中的非零元素所在的 位置必对应A 位置必对应A或B中的一个非零元素,所以加法运 算不会在A 算不会在A和B中原非零元素之外的其他位置上生 成新的非零元素。
矩阵的线性运算(2) 矩阵的线性运算
考虑三元组表示法中的矩阵加法。由于 都需要按顺序储存各个元素,我们应当 按顺序对每个A 按顺序对每个A或B中非零的位置做加法。 下面有一个例子:
1 0 0 0 0 2 1 0 2 0 5 0 + 3 0 0 = 3 5 0 0 2 0 0 7 0 0 9 0
矩阵的线性运算(3) 矩阵的线性运算
我们记录两个矩阵A 我们记录两个矩阵A,B的当前非零元素 序号pA和pB。 序号pA和pB。为了保证结果的有序性, 我们每次比较这两个当前元素的位置。
如果A的当前位置靠前,则把A的第pA个元素加入 如果A的当前位置靠前,则把A的第pA个元素加入 矩阵C 并使pA=pA+1 矩阵C,并使pA=pA+1 如果B的当前位置靠前,则把B的第pB个元素加入 如果B的当前位置靠前,则把B的第pB个元素加入 矩阵C 并使pB=pB+1 矩阵C,并使pB=pB+1 如果当前位置相同,则先使pA=pA+1, pB=pB+1。 如果当前位置相同,则先使pA=pA+1, pB=pB+1。 如果A的第pA- 个元素和B的第pB如果A的第pA-1个元素和B的第pB-1个元素的和不为 零,则把它加到矩阵C 零,则把它加到矩阵C中。
稀疏矩阵的十字链表表示(4) 稀疏矩阵的十字链表表示
为了方便的找到每一个包含非零元素的 行和列,我们把所有行串在一起,组成 一个行链表,把所有列也串在一起,组 成一个列链表。像这样:
struct TRow { int RowNo; TNode * firstnode; }; struct TCol { int ColNo; TNode * firstnode; };
稀疏矩阵的三元组表示法
显然,表示稀疏矩阵最直接的方法就是 仅记录下非零元素的个数L和这L 仅记录下非零元素的个数L和这L个元素 的位置(row,col)和大小(value), 的位置(row,col)和大小(value),即下面这 个结构:
struct TMatrix2 { int l; int row[MAXL],col[MAXL],value[MAXL]; };
稀疏矩阵的十字链表表示(1) 稀疏矩阵的十字链表表示
三元组表示法比较好的解决了稀疏矩阵的空间存储问 题,却忽视了稀疏矩阵可能进行了一些基本操作。 考虑两个稀疏矩阵A 考虑两个稀疏矩阵A和B相加的问题。对于运算结果矩 阵C来说,可能会因为正负抵消而产生出很多新的零元 素和非零元素,导致三元组需要进行一些插入和删除 操作。当这些操作很频繁的时候,程序的速度会明显 变慢。 在某些特定情况下,我们需要对元素进行检索,由于 三元组的元素之间联系并不紧密,所以检索很不方便。
矩阵的判重 矩阵的线性运算 矩阵的转置 矩阵乘法
矩阵的判重
在二维数组表示法中,我们可以用一个二重循 环判断两个矩阵是否相等。 在三元组表示方法中,我们如果保证非零元素 是按照从上到下,从左到右的顺序储存的,则 可以用一个循环直接判断。但如果不能保证, 则需要二重循环。因此在未加说明的情况下, 三元组表示法均需要按顺序保存各个元素。 在十字链表表示方法中,我们需要依次遍历每 一个非零行(或者列)。
首先,求出每一列第一非零元素转置后的序号。这一步只需 要统计一下每列的非零元素的个数就可以了。 由于每一列的元素在转置以后的先后次序保持不变,每个元 素就可以直接写到该行的下一个空闲位置。
矩阵的转置(4) 矩阵的转置
请看下面的例子:
0 3 0 5 1 0 2 0 6 0 0 1 0 0 0 0 4 0 0 2 3 0 5 6 0 4 0 0 0 0 0 0 各列非零元素个数为:2,3,0,1 各列第一个元素序号:0,2,5,5 3 5 1 6 4 2 0 1 2 3 4 5
Aij前面的数的个数为: (n − l + 1) + ( j − 1) ∑ 计算得: 计算得:
1 k = (2n − i + 2)(i − 1) + j 2
稀疏矩阵
在前面的二维数组表示法中,我们表示 一个N*M的矩阵需要N*M个内存单元。 一个N*M的矩阵需要N*M个内存单元。 如果已知矩阵中存在着大量的0 如果已知矩阵中存在着大量的0元素,那 么这种表示方法是很浪费空间的。 由于非零元素的个数L 由于非零元素的个数L十分有限,我们可 以只储存下这L 以只储存下这L个元素的位置和大小,占 用的空间便会少得多。
普通的方法需要进行8次乘法。
矩阵乘法(3) 矩阵乘法
Strassen的新方法如下: Strassen的新方法如下:
矩阵的线性运算(4) 矩阵的线性运算
十字链表表示法下的加法可以一行行的做。即: