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

发布于:2025-07-24 ⋅ 阅读:(25) ⋅ 点赞:(0)

数组和特殊矩阵

数组

数组的定义

数组是由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+isizeof(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+(jM+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+(iN+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=0an=i−1a_n = i - 1an=i1n=in = in=i。因此,第iii行的起始下标为i(i−1)2\frac{i(i-1)}{2}2i(i1)。接着我们加上j的偏移量,得到下标为i(i−1)2+j−1\frac{i(i-1)}{2} + j - 12i(i1)+j1。(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(i1)i+j1,2(j1)j+i1, ij(下三角区和主对角线元素) 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=0an=j−1a_n = j - 1an=j1n=jn = jn=j。因此,第jjj列的起始下标为j(j−1)2\frac{j(j-1)}{2}2j(j1)。接着我们加上i的偏移量,得到下标为j(j−1)2+i−1\frac{j(j-1)}{2} + i - 12j(j1)+i1。(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(j1)+i1,2i(i1)+j1,if ijif 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(i1)i+j1,2n(n+1), if ij(下三角区和主对角线元素)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(i1)2(i1)(i2)=2(i1)(2ni+2),其中,n(i−1)n(i-1)n(i1)代表该矩阵第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(i1)(2ni+2)+(ji)2n(n+1)ij(上三角区和主对角线元素)i>j(下三角区元素)

三对角矩阵

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

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(i1)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(i1)1+ji+21=2i+j3

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

由一维数组下标 kkk 求矩阵下标 i,ji, ji,j 的方法如下:首先确定行号 iii,由于前 i−1i-1i1 行共 3(i−1)−13(i-1)-13(i1)1 个元素,前 iii 行共 3i−13i-13i1 个元素,因此有 3(i−1)−1<k+1≤3i−13(i-1)-1 < k+1 \leq 3i-13(i1)1<k+13i1,即 i≥k+23i \geq \frac{k+2}{3}i3k+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(i1)1k<3i1,即 i≤k+13+1i \leq \frac{k+1}{3} + 1i3k+1+1,向下取整得 i=⌊k+13⌋+1i = \lfloor \frac{k+1}{3} \rfloor + 1i=3k+1+1。确定 iii 后,前 i−1i-1i1 行共 3(i−1)−13(i-1)-13(i1)1 个元素,第 iii 行的第一个元素下标为 k0=3(i−1)−1k_0 = 3(i-1)-1k0=3(i1)1,所以列号 j=k−k0−2+ij = k - k_0 - 2 + ij=kk02+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+2j=k[3(i1)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(i1)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):

md1ebaq6.png