【计算机考研(408)- 数据结构】数组和特殊矩阵

数组和特殊矩阵

数组

数组的定义

数组 是由n(n>=1)个相同类型的数据元素构成的有限序列。每个数据元素称为一个数组元素 ,每个元素在n个线性关系中的序号称之为该元素的下标 ,下标的取值范围称为数组的维界

数组是[[线性表]]的推广,一维数组可视为一个线性表,二维数组可视为其元素是定长数组的线性表。

数组的存储结构

各数组元素大小相同,且物理上连续存放。

我们用一个 Markdown 表格模拟数组的地址存储结构(ElemType a[7]):

其他 a[0] a[1] a[2] a[3] a[4] a[5] a[6] 其他
LOC LOC+1 ... ... ... ... ... ...

数组元素a[i]的存放地址=LOC+i∗sizeof(ElemType) 数组元素a[i]的存放地址= LOC + i * sizeof(ElemType) 数组元素a[i]的存放地址=LOC+i∗sizeof(ElemType)

每个单元格表示数组元素在内存中的连续存储位置,数组元素之间没有间隔,便于通过下标快速访问。

接下来假设有一个二维数组() ElemType b[2][4],我们可以用一个 Markdown 表格模拟其地址存储结构和逻辑结构:

逻辑结构:

b[0][0] b[0][1] b[0][2] b[0][3]
b[1][0] b[1][1] b[1][2] b[1][3]

但是在内存中,数组是连续的,那么也就有了行优先和列优先的存储方式。

列优先存储结构:

其他 b[0][0] b[1][0] b[0][1] b[1][1] b[0][2] b[1][2] b[0][3] b[1][3] 其他
... LOC+0 LOC+1 LOC+2 LOC+3 LOC+4 LOC+5 LOC+6 LOC+7 ...

M行N列的二维数组b[M][N]中,若按列优先存储,则:

b[i][j]的存放地址=LOC+(j∗M+i)∗sizeof(ElemType) b[i][j]的存放地址= LOC + (j * M + i) * sizeof(ElemType) b[i][j]的存放地址=LOC+(j∗M+i)∗sizeof(ElemType)

行优先存储结构:

其他 b[0][0] b[0][1] b[0][2] b[0][3] b[1][0] b[1][1] b[1][2] b[1][3] 其他
... LOC+0 LOC+1 LOC+2 LOC+3 LOC+4 LOC+5 LOC+6 LOC+7 ...

M行N列的二维数组b[M][N]中,若按行优先存储,则:

b[i][j]的存放地址=LOC+(i∗N+j)∗sizeof(ElemType) b[i][j]的存放地址= LOC + (i * N + j) * sizeof(ElemType) b[i][j]的存放地址=LOC+(i∗N+j)∗sizeof(ElemType)

矩阵的存储和特殊矩阵的压缩存储

压缩存储 是指为多个值相同的元素只分配一个存储空间,对零元素不分配空间。
特殊矩阵 就是指分布有一定规律的矩阵。
压缩存储方法:找出分布规律,把那些呈现规律性分布的、值相同的元素放在同一个存储空间中。

对称矩阵

a1,1a_{1,1}a1,1 a1,2a_{1,2}a1,2 ⋯\cdots⋯ a1,na_{1,n}a1,n
a2,1a_{2,1}a2,1 a2,2a_{2,2}a2,2 ⋯\cdots⋯ a2,na_{2,n}a2,n
⋮\vdots⋮ ⋮\vdots⋮ ⋱\ddots⋱ ⋮\vdots⋮
an,1a_{n,1}an,1 an,2a_{n,2}an,2 ⋯\cdots⋯ an,na_{n,n}an,n

如上所示的矩阵是一个n阶矩阵,若对于任意的i,ji,ji,j,都有ai,j=aj,ia_{i,j}=a_{j,i}ai,j=aj,i,则称之为对称矩阵 。其中的元素也可以根据i,j的大小分为三个部门:i=ji=ji=j主对角线,i>ji>ji>j 下三角区,i<ji<ji<j 上三角区。

普通存储:n*n二维数组,因为上下两个三角区数值相同,所以可以压缩存储。

压缩存储策略:只存储主对角线+下三角区(或主对角线+上三角区)

同时我们需要按照行/列优先的原则存入到一个一维数组中,并建立i,j(矩阵下标)与数组下标的映射关系。

以下以行优先为例:

对于i>=j的下标来说,第一个存储的是 i=1,j=1,第二个存储的是 i=2,j=1,接着 2,23,13,23,3。分别映射到了0,1,2,3上面。

根据不难看出,对于i是累加的,比如第一行起始是0,第二行起始是1,到了第三行起始就变为了3。那么其实他就是一个等差数列。

根据等差数列求和公式:n(a1+an)2\frac{n(a_1 + a_n)}{2}2n(a1+an),其中a1=0a_1 = 0a1=0,an=i−1a_n = i - 1an=i−1,n=in = in=i。因此,第iii行的起始下标为i(i−1)2\frac{i(i-1)}{2}2i(i−1)。接着我们加上j的偏移量,得到下标为i(i−1)2+j−1\frac{i(i-1)}{2} + j - 12i(i−1)+j−1。(j-1的原因是因为数组下标从0开始)

至此我们把矩阵中下三角的元素都存储了,正因为ai,j=aj,ia_{i,j} = a_{j,i}ai,j=aj,i,对于上三角的元素,我们直接替换下标即可。

得到最终的下标转换公式如下:

k={(i−1)i2+j−1,当 i≥j(下三角区和主对角线元素)(j−1)j2+i−1,当 i<j(上三角区元素) k = \begin{cases} \frac{(i-1)i}{2} + j-1, & \text{当 } i \geq j \text{(下三角区和主对角线元素)} \\ \frac{(j-1)j}{2} + i-1, & \text{当 } i < j \text{(上三角区元素)} \end{cases} k={2(i−1)i+j−1,2(j−1)j+i−1,当 i≥j(下三角区和主对角线元素)当 i<j(上三角区元素)

按照上面的方法,分析列优先:

对于i>=j的下标来说,第一个存储的是 i=1,j=1,第二个存储的是 i=1,j=2,接着 2,21,32,33,3。分别映射到了0,1,2,3上面。

根据等差数列求和公式:n(a1+an)2\frac{n(a_1 + a_n)}{2}2n(a1+an),其中a1=0a_1 = 0a1=0,an=j−1a_n = j - 1an=j−1,n=jn = jn=j。因此,第jjj列的起始下标为j(j−1)2\frac{j(j-1)}{2}2j(j−1)。接着我们加上i的偏移量,得到下标为j(j−1)2+i−1\frac{j(j-1)}{2} + i - 12j(j−1)+i−1。(i-1的原因是因为数组下标从0开始)

至此,我们得到列优先的公式为:

k={j(j−1)2+i−1,if i≥ji(i−1)2+j−1,if i<j k = \begin{cases} \frac{j(j-1)}{2} + i - 1, & \text{if } i \geq j \\ \frac{i(i-1)}{2} + j - 1, & \text{if } i < j \end{cases} k={2j(j−1)+i−1,2i(i−1)+j−1,if i≥jif i<j

对于存储上三角的方法,我不想写了,留给我自己思考去吧

三角矩阵

a1,1a_{1,1}a1,1 CCC CCC CCC
a2,1a_{2,1}a2,1 a2,2a_{2,2}a2,2 CCC CCC
a3,1a_{3,1}a3,1 a3,2a_{3,2}a3,2 a3,3a_{3,3}a3,3 CCC
a4,1a_{4,1}a4,1 a4,2a_{4,2}a4,2 a4,3a_{4,3}a4,3 a4,4a_{4,4}a4,4

下三角矩阵:除了主对角线和下三角区,其余的元素都相同(如表格所示)

上三角矩阵:除了主对角线和上三角区,其余的元素都相同(如表格所示)

压缩存储策略 (下三角矩阵为例):按行优先原则将下三角区存入一维数组中。并在最后一个位置存储常量C,上面分析的下三角区存储公式可以直接使用。而常数C的存储位置为n(n+1)/2n(n+1)/2n(n+1)/2

存储下三角矩阵的下标转换公式:

k={(i−1)i2+j−1, if i≥j(下三角区和主对角线元素)n(n+1)2,if i<j(上三角区元素) k = \begin{cases} \frac{(i-1)i}{2} + j - 1, & \ \text{if } i \geq j (下三角区和主对角线元素) \\ \frac{n(n+1)}{2}, & \text{if } i < j (上三角区元素) \end{cases} k={2(i−1)i+j−1,2n(n+1), if i≥j(下三角区和主对角线元素)if i<j(上三角区元素)

下面开始分析上三角矩阵的存储,还是上面的策略,常数C的存储位置为n(n+1)/2n(n+1)/2n(n+1)/2。

那么对于第i行应当开始存储的位置应该为上i-1行的元素个数,应当为n(i−1)−(i−1)(i−2)2=(i−1)(2n−i+2)2n(i-1)-\frac{(i-1)(i-2)}{2}=\frac{(i-1)(2n-i+2)}{2}n(i−1)−2(i−1)(i−2)=2(i−1)(2n−i+2),其中,n(i−1)n(i-1)n(i−1)代表该矩阵第i行之前一共的元素数目,之后的式子代表下三角区的累计数目,对于第j列,应为相对于对角线的偏移量即为(j-i)。

因此我们可以得到存储上三角矩阵的下标转换公式:

k={(i−1)(2n−i+2)2+(j−i)i≤j(上三角区和主对角线元素)n(n+1)2i>j(下三角区元素) k = \begin{cases} \frac{(i-1)(2n-i+2)}{2} + (j-i) & i \leq j (上三角区和主对角线元素) \\ \frac{n(n+1)}{2} & i > j (下三角区元素) \end{cases} k={2(i−1)(2n−i+2)+(j−i)2n(n+1)i≤j(上三角区和主对角线元素)i>j(下三角区元素)

三对角矩阵

三对角矩阵,又称带状矩阵:当∣i−j∣>1|i-j|>1∣i−j∣>1时,元素值为0。(1≤i,j≤n)(1\leq i,j\leq n)(1≤i,j≤n)

a1,1a_{1,1}a1,1 a1,2a_{1,2}a1,2 0 0 0 0
a2,1a_{2,1}a2,1 a2,2a_{2,2}a2,2 a2,3a_{2,3}a2,3 0 0 0
0 a3,2a_{3,2}a3,2 a3,3a_{3,3}a3,3 a3,4a_{3,4}a3,4 0 0
0 0 a4,3a_{4,3}a4,3 a4,4a_{4,4}a4,4 a4,5a_{4,5}a4,5 0
0 0 0 a5,4a_{5,4}a5,4 a5,5a_{5,5}a5,5 a5,6a_{5,6}a5,6
0 0 0 0 a6,5a_{6,5}a6,5 a6,6a_{6,6}a6,6

压缩存储策略:按行优先(或列优先)原则,只存储带状部分

那么按行优先规则,前i-1行的元素个数为3(i−1)−13(i-1)-13(i−1)−1,第i行的元素个数为3,最后一行的元素个数为2。那么ai,ja_{i,j}ai,j是第i行的第j-i+2(如果下标从1开始,从零开始还要减1)个元素。所以我们得到如下的公式:

k=3(i−1)−1+j−i+2−1=2i+j−3 k=3(i-1)-1+j-i+2-1=2i+j-3 k=3(i−1)−1+j−i+2−1=2i+j−3

接下来我们需要讨论一下逆向,即通过k求i和j:

由一维数组下标 kkk 求矩阵下标 i,ji, ji,j 的方法如下:首先确定行号 iii,由于前 i−1i-1i−1 行共 3(i−1)−13(i-1)-13(i−1)−1 个元素,前 iii 行共 3i−13i-13i−1 个元素,因此有 3(i−1)−1<k+1≤3i−13(i-1)-1 < k+1 \leq 3i-13(i−1)−1<k+1≤3i−1,即 i≥k+23i \geq \frac{k+2}{3}i≥3k+2,向上取整得 i=⌈k+23⌉i = \lceil \frac{k+2}{3} \rceili=⌈3k+2⌉。

也可以按照王道书的逻辑,3(i−1)−1≤k<3i−13(i-1)-1 \leq k < 3i-13(i−1)−1≤k<3i−1,即 i≤k+13+1i \leq \frac{k+1}{3} + 1i≤3k+1+1,向下取整得 i=⌊k+13⌋+1i = \lfloor \frac{k+1}{3} \rfloor + 1i=⌊3k+1⌋+1。确定 iii 后,前 i−1i-1i−1 行共 3(i−1)−13(i-1)-13(i−1)−1 个元素,第 iii 行的第一个元素下标为 k0=3(i−1)−1k_0 = 3(i-1)-1k0=3(i−1)−1,所以列号 j=k−k0−2+ij = k - k_0 - 2 + ij=k−k0−2+i。公式总结如下:

i=⌈k+23⌉j=k−[3(i−1)−1]−2+i \begin{aligned} &i = \lceil \frac{k+2}{3} \rceil \\ &j = k - [3(i-1)-1] - 2 + i \end{aligned} i=⌈3k+2⌉j=k−[3(i−1)−1]−2+i

或者

i=⌊k+13⌋+1j=k−[3(i−1)−1]−2+i \begin{aligned} &i = \lfloor \frac{k+1}{3} \rfloor + 1 \\ &j = k - [3(i-1)-1] - 2 + i \end{aligned} i=⌊3k+1⌋+1j=k−[3(i−1)−1]−2+i

先根据 kkk 计算 iii,再用 iii 计算 jjj,两种取整方式结果一致。

稀疏矩阵

稀疏矩阵:非零元素远远少于矩阵元素的个数

压缩存储策略:顺序存储------三元组<行,列,值>

例如,下面的稀疏矩阵:

0 0 4 0 0 5
0 3 0 9 0 0
0 0 0 0 7 0
0 2 0 0 0 0
0 0 0 0 0 0

其三元组顺序存储形式为:(行、列标从0开始)

i行 j列 v值
0 2 4
0 5 5
1 1 3
1 3 9
2 4 7
3 1 2

行、列标从1开始

1 3 4
1 6 5
2 2 3
2 4 9
3 5 7
4 2 2

既然有顺序存储,那么会有链式存储的方式。压缩存储策略二:链式存储------十字链表法

一个结点包含五部分内容:行、列、值、指向下一个同列元素的指针、指向下一个同行元素的指针。

还是上面的矩阵,如果给他创建一个十字链表,那么就会得到如下结果(图源王道课PPT):

相关推荐
秋说3 分钟前
【PTA数据结构 | C语言版】哥尼斯堡的“七桥问题”
c语言·数据结构·算法
刚入坑的新人编程1 小时前
暑期算法训练.5
数据结构·c++·算法
胡耀超2 小时前
Oracle数据库索引性能机制深度解析:从数据结构到企业实践的系统性知识体系
数据结构·数据库·oracle·dba·b+树·索引
Jassy1592 小时前
C++二叉搜索树
数据结构·c++·学习
AICodeThunder2 小时前
图论(2):最短路
数据结构·算法·图论
lightqjx3 小时前
【数据结构】链表(linked list)
数据结构·链表
秋说3 小时前
【PTA数据结构 | C语言版】求单源最短路的Dijkstra算法
c语言·数据结构·算法·图论
xienda3 小时前
归并排序:优雅的分治排序算法(C语言实现)
数据结构·算法·排序算法
zl_dfq4 小时前
数据结构 之 【排序】(直接插入排序、希尔排序)
数据结构
秋说4 小时前
【PTA数据结构 | C语言版】多叉堆的上下调整
c语言·数据结构·算法