矩阵的压缩存储
前言
一入编程深似海,从此砖头是爱人,日日搬,夜夜搬,搬到天荒地老,精尽人亡,直教人失去了自我,忘记了时间,忽然之间发现九月份快没了,赶紧写篇博客打个卡,证明一下我还活着。。。
数组与矩阵
数组是由一组相同类型的数据元素构成的有限序列,访问数据元素的方式是使用元素各自的序号进行访问,也就是下标。数组它本身是线性表的推广,一维数组就是一个向量形式的线性表,二维数组就是由一维数组组成的线性表。
在许多科学计算和工程应用中,经常要用到矩阵的概念,我们用的最多的其实就是Mysql的表,表数据都是行列存储,这就是矩阵。
由于矩阵具有元素数目固定以及元素按下标关系有序排列等特点,所以在使用高级语言编程时,一般都是用二维数组来存储矩阵。
数组的顺序存储
为什么是顺序存储?
我想问这个问题就太低级了。因为它是数组,数据的存储方式分为顺序存储和链式存储两种,数组一旦被定义,他的维数和维界就已固定,除结构的初始化和销毁外,数组只会有存取元素和修改元素的操作,不存在插入和删除操作,所以数组适合用顺序存储。
数组存放在内存中的映射关系
数组可以是多维的,但是内存空间却是一维的,所以我们就要把多维数组通过一定的映射顺序把它变成一维的,然后存储到内存空间之中。
在大多数高级编程语言中,多维数组在内存中通常有两种不同的顺序存储方式,按行优先顺序存储 和 按列优先顺序存储。
举个例子,以下3行4列的一个二维数组矩阵:
a1,a2,a3,a4
b1,b2,b3,b4
c1,c2,c3,c4
按行优先顺序存储:
按列优先顺序存储:
地址计算
地址计算的意思就是给定数组下标,求在一维内存空间的地址,从而取出数据。
我们先来看一维数组的地址计算
一维数组内的元素只有一个下标,存储方法和普通的线性表一样。
如一维数组 A = [a1,a2,a3,……ai,………,an],每个元素占用size个存储单元(就是内存大小),那么元素ai的存储地址为 A[0]的位置 + (i-1)*size
再来看二维数组的地址计算
以二维数组Amn为例,首元素为A[0][0],数组中任意元素A[i][j]的地址为:A[0][0]的位置 + (n * (i-1) + (j-1))* size;
比如:一个5行4列的二维数组A,按行存储,其中每个元素占2个存储单元,首元素地址是1000,求第3行第2列的元素在内存中的地址。
我们把参数套进公式中,答案 = 1000 + (4 * (3-1) + (2-1)) * 2 = 1018;
如果把矩阵画在纸上观察就一目了然:
公式的内容就是求出格子数,乘以每个格子所占用的存储单元,再加上首地址。
矩阵转置
设计一个算法,实现矩阵A(m*n) 转置为矩阵B(n*m),简单的说,就是行列互换。
$arr = [ ['张三','男','北京'], ['李四','女','上海'], ['王五','男','广州'], ]; function transpose($a){ $b = []; for ($i = 0;$i < count($a); $i ++){ for($j = 0;$j < count($a[$i]); $j ++){ $b[$j][$i] = $a[$i][$j]; } } return $b; } $result = transpose($arr);
结果为:
$result = [
[‘张三’,’李四’,’王五’],
[‘男’,’女’,’男’],
[‘北京’,’上海’,’广州’],
];
特殊矩阵
特殊矩阵的压缩存储
特殊矩阵指的是具有许多相同元素或者零元素,并且这些元素的分布有一定规律性的矩阵。
这种矩阵如果还使用前面的方式来存储,就会产生大量的空间浪费,为了节省存储空间,可以对这类矩阵采用压缩存储,压缩存储的方式是把那些呈现规律性分布的相同元素只分配一个存储空间,对零元素不分配存储空间。
三角矩阵
三角矩阵我们以下三角来做例子,如图所示:
所有空格之中装的数据都是null或者都是同一常量,也就是空格中全都是相同的数据。
按行方式存储的情况下,一维存储内存空间的大小是:1+2+3+4+5+6+7 = n(n+1)/2 = 7 * (7+1) / 2 = 28,当然,在最后还要加一个存储空间,用来存储上三角中相同的数据。
那么对于任意元素aij,在一维存储内存空间中的地址仍然是要靠计算格子来得到,先算出占满行的总格子数,再加上当前行的格子数:a[0][0]的位置 + (i * (i+1) / 2 + j) * size;
我们使用公式来验证一下,a42的所在格子数 = (i * (i+1) / 2 + j) = 4 * 5 / 2 + 2 = 12
带状矩阵
带状矩阵也叫做对角矩阵,如图所示:
带状矩阵的特征是:所有非0元素都集中在以主对角线为中心的3条对角线区域,其他区域的元素都为0。
除了第一行和最后一行仅2个非零元素,其余行都是3个非零元素,换句话说就是每行都是3个非零元素,但是第一行少了1个,最后一行少了1个,所以所需的一维空间大小为:3n – 2;
那么对于任意一个元素 aij,怎么计算它在内存空间的地址呢?
经过观察可以得知i和j都在对角线附近,相减后的结果与分布情况分别如下
j – i = 1;对角线上面
j – i = 0; 对角线
j – i = -1;对角线下面
不管是在对角线的哪个位置,我们都可以使用通用的办法来计算地址,也就是先计算出上面行所占的格子,再加上当前行的格子。
上面的行数:i,由于行列都是0开头计数,所以上面的行数就是i这个值。
上面的格子数: 3 * i – 1,减1是因为第一行少一个格子。
当前行格子数: j – i + 1;根据i和j的关系,我们把相减后的值加1,得到当前行的格子数。
那么最后aij的内存地址 = a00首地址 + ((3 * i -1) + ( j-i+1)) * size; size为每个数据所占用的存储单元大小。
比如首地址为1000,每个数据占用2个存储单元,那么a45在内存中的地址 = 1000 + 13 * 2 = 1026;
稀疏矩阵的压缩存储
由于特殊矩阵中非零元素的分布是有规律的,所以总是可以找到矩阵元素与一维数组下标的对应关系,但还有一种矩阵,矩阵中大多数元素都为0,一般情况下非零元素个数只占矩阵元素总数的30%以下,并且元素的分布是没有任何规律的,这样的矩阵我们称为稀疏矩阵。
如果采用常规方法存储稀疏矩阵,就会相当浪费存储空间,因此我们需要只存储非零元素。由于稀疏矩阵中非零元素的分布是没有规律的,所以除了存储非零元素的值之外,我们还需要同时存储非零元素的行、列位置,也就是三元组(i,j,aij)。
如图:
所谓三元组,也就是一个矩阵,一个二维数组,每一行都三个列,分别为行号、列号、元素值。
由于三元组在稀疏矩阵与内存地址间扮演了一个中间人的角色,所以稀疏矩阵进行压缩存储后,便失去了随机存取的特性。