360 likes | 648 Views
第四章 数组与特殊矩阵 第一节 数组 第二节 特殊矩阵的压缩存储 第三节 稀疏矩阵 本 章 小 结 实 训 思 考 与 习 题. 第四章 数组与特殊矩阵. 学习要求: 数组是一种比较常见的数据结构,本章的目标是让读者了解数组的特性、数组的表示方法,掌握数组的逻辑结构及内存的存储结构,熟悉几种特殊矩阵的压缩存储方法。 主要内容: 本章主要讨论数组的基本概念和逻辑结构、存储方式以及几种特殊矩阵的压缩存储方法。. 第一节 数组.
E N D
第四章 数组与特殊矩阵 第一节 数组 第二节 特殊矩阵的压缩存储 第三节 稀疏矩阵 本 章 小 结 实 训 思 考 与 习 题
第四章 数组与特殊矩阵 学习要求: 数组是一种比较常见的数据结构,本章的目标是让读者了解数组的特性、数组的表示方法,掌握数组的逻辑结构及内存的存储结构,熟悉几种特殊矩阵的压缩存储方法。 主要内容: 本章主要讨论数组的基本概念和逻辑结构、存储方式以及几种特殊矩阵的压缩存储方法。
第一节 数组 数组是人们很熟悉的数据类型,几乎所有的程序设计语言中都设有数组类型。我们把多维数组看成是广义的线性表,即看成是这样一个线性表:它的每一个数据元素都是一个定长线性表,或理解成多维数组对应的线性表中的数据元素本身又是一个线性表。本节重点讨论二维数组的逻辑结构及其内存映像,多维数组可以在二维数组的分析基础上加以推广。 一、数组的基本概念 高级语言中的数组在计算机内是用一批连续的内存单元来表示的,称为数组的顺序存储结构。在实际使用中还可以根据需要选择数组的其他存储方式。 数组是由下标(index)和值(value)组成的序对的集合。在数组中,一旦给定下标,都存在一个与其相对应的值,这个值就称为数组元素。 数组是一个固定格式和数量的数据有序集,每一个数据元素用唯一的一个或一组下标标识,在数组上不能做插入、删除数据元素的操作。数组
一旦被定义,它的维数和维界就不再改变。因此,数组的运算通常只有两种基本运算: 1.给定一组下标,存取相应的数据元素。 2.给定一组下标,修改相应的数据元素的值。 二、数组的逻辑结构 数组中的数据元素均属于同一数据类型。在二维数组中,每个元素都受行和列关系的约束。设定A是一个二维数组,如图4.1(a)所示,以m行n列的矩阵形式表示。 图4.1(a) m行n列的二维数组
图4.1(b) 行向量形式的二维数组 图4.1(c) 列向量形式的二维数组
它可以看成是一个一维数组,即可看成是一个线性表A=(a1,a2,…,an),其中,每一个元素ai对应一个列向量形式的线性表ai=(a1i,a2i,…,ami),如图4.1(c)所示。这样我们就可以把二维数组看成是由几个列向量组成的线性表了。同样,可以把二维数组A看成是一个线性表A=(a1, a2 ,…, am),其中,每一个元素aj对应一个行向量形式的线性表aj=(aj1,aj2,aj3,…,ajn),如图4.1(b)所示。 二维数组可以看作“数据元素是一维数组”的一维数组,三维数组可以看作“数据元素是二维数组”的一维数组,依此类推。
三、数组的内存映像 现在来讨论数组在计算机中的内存表示。通常,数组在内存被映像为向量,即向量作为数组的一种存储结构,数组是以顺序存储方式存放在计算机中的,而随机存取是顺序存储结构的主要特性。 对于一维数组,可根据数组元素的下标得到它的存储地址,按下标顺序分配即可。 对于二维数组,可用向量(顺序)存储结构来存放。用向量结构来存放二维数组中的元素,一定要按某种次序将元素排成一个线性序列,顺序存放的次序有两种规则: 1.先行后列顺序,或者称为行优先顺序,在PASCAL和C语言中,数组就是按行优先顺序存储的。 2.先列后行顺序,或者称为列优先顺序,在FORTRAN语言中,数组就是按列优先顺序存储的。 例如,B数组是个5行3列的二维数组,对应的两种规则的顺序存储结构如图4.2所示。
图4.2 B数组的两种顺序存放示意图 以上规则可以推广到多维数组:以行优先顺序的分配规律是:最右边的下标先变化,即最右边的下标从小到大,循环一遍后,右边的第2个下标再变化,……,从右向左,最后是左下标。以列优先顺序的分配规律恰好与之相反:最左边的下标先变化,即最左边的下标从小到大,循环一遍后,左边的第2个下标再变化,……,从左向右,最后是右下标。 对于顺序存储的数组,只要知道向量的起始地址,数组的行号数和列号数,以及每个数组元素所占用的存储单元个数,就可以求得给定下标的数组元素的存储起始地址。
例如,一个二维数组A(m×n)按行优先存储在向量中。设定数组中第一个元素的存储起始地址为LOC(a1,1),并已知某个数据元素的下标为i和j(1≤i≤m,1≤j≤n),及每个数据元素占用的存储单元数为b,就可以计算该数据元素的存储起始地址:例如,一个二维数组A(m×n)按行优先存储在向量中。设定数组中第一个元素的存储起始地址为LOC(a1,1),并已知某个数据元素的下标为i和j(1≤i≤m,1≤j≤n),及每个数据元素占用的存储单元数为b,就可以计算该数据元素的存储起始地址: LOC(ai,j)=LOC(a1,1)+[n*(i-1)+(j-1)]*b 值得一提的是:在C语言中,数组下标的下界是0,因此在C语言中,如果数组元素从0下标存放起,地址的计算公式是: LOC(ai,j)=LOC(a0,0)+(i*n+j)*b 若二维数组A(m×n)按列优先顺序存储在向量中,则相应A(i,j)的地址的计算公式为: LOC(ai,j)=LOC(a1,1)+[m*(j-1)+(i-1)]*b 或 LOC(ai,j)=LOC(a0,0)+(m*j+i)*b
第二节 特殊矩阵的压缩存储 在科学与工程计算问题中,矩阵是一种常用的数学对象,在用高级语言程序时,简单而又自然的方法,就是将一个矩阵描述为一个二维数组。矩阵在这种存储表示之下,可以对其元素进行随机存取,各种矩阵运算也非常简单。这样,利用上一节的地址计算公式可以快速访问矩阵中的每个元素。然而,实际应用中会遇到一些特殊矩阵,所谓特殊矩阵,是指矩阵中值相同的元素或者零元素的分布有一定的规律。例如,对称矩阵、三角矩阵和带状矩阵都是特殊矩阵。 为了节省存储空间,可以对这些矩阵进行压缩存储。所谓压缩存储就是为多个值相同的元素只分配给一个存储空间,对零元素不分配存储空间。由于特殊矩阵中非零元素的分布有明显的规律,因此我们可将其压缩存储到一个一维数组中,并找到每个非零元素在一维数组中的对应关系。 一、对称矩阵 (一) 对称矩阵 若一个n阶矩阵A中的元素满足下述性质: aij=aji (1≤i≤n,1≤j≤n)
则称A为n阶对称矩阵。例如A矩阵是一个5阶的对称矩阵,则称A为n阶对称矩阵。例如A矩阵是一个5阶的对称矩阵, 如图4.3所示。 (二)对称矩阵的压缩存储 对于对称矩阵,可以为每一对对称矩阵元素分配一个存储空间,则可以将n2个元素压缩存储到n(n+1)/2个空间中,节约了n(n-1)/2个存储单元。当n较大时,这是相当可观的一部分存储资源。 下面以行优先存储其下三角(包括对角线)中的元素。 在下三角矩阵中共有n(n+1)/2个元素,可开辟一个长度为n(n+1)/2的一维数组B,然后一行接一行地依次存放对称矩阵中下三角(包括对角线)部分的元素。经压缩后,存放在一维数组B中的形式如图4.4所示。
图4.4 对称矩阵的压缩存储 显然,对称矩阵A用一维数组B表示后,可以按如下原则访问对称矩阵A中的第i行、第j行的元素aij。 1如果aij为下三角部分元素(j≤i),则它存放在一维数组B的第i(i-1)/2+j个元素中。 2如果aij为非下三角元素(j>i),则它可以对应一维数组B的第j(j-1)/2+i个元素的值。即有如下关系: aij=B[i(i-1)/2+j]当j≤i B[j(j-1)/2+i]当j>i
下三角矩阵A也可以用以列优先的方式压缩在一维数组B中,请读者自己练习。下三角矩阵A也可以用以列优先的方式压缩在一维数组B中,请读者自己练习。 若对图4.3的5阶对称矩阵进行以行优先压缩存储,其结果如图4.5所示。 图4.5对称矩阵A的压缩存储 二、三角矩阵 三角矩阵分为下三角矩阵和上三角矩阵。所谓下(上)三角矩阵是指矩阵的上(下)三角(不包括对角线)中的元素均为常数c或0的n阶矩阵。例如,B矩阵是一个下三角矩阵;C矩阵是一个上三角矩阵,如图4.6所示。 下面分别下面讨论它们的压缩存储方法。
图4.6 三角矩阵 (一)下三角矩阵 下三角矩阵的压缩存储与对称矩阵类似,不同之处在于存完下三角矩阵中的元素之后,紧接着要存储对角线上方的常量c,因为是同一个常数,所以再加上一个常数c的存储空间即可,这样一共就存储了n(n+1)/2+1个元素。 在实际应用中,下三角矩阵一般应采用以行优先压缩存储的方法,因为在以列优先存储下三角矩阵,访问下三角矩阵中的元素时,其下标运算要比以行优先压缩存储复杂一些。 (二)上三角矩阵 对于上三角矩阵,压缩存储的思想与下三角矩阵类似,但采用以列优先存储比较方便,其存储形式如图4.7所示。
图4.7上三角矩阵的压缩存储 在以列优先压缩存储上三角矩阵的情况下,访问上三角矩阵中第i行、第j行元素aij的公式为:
三、带状矩阵 一个n阶方阵,如果它的全部非零元素落在一个以对角线为中心的带状区域中,则称该矩阵为带状矩阵。若有一n阶矩阵A为带状矩阵,如果存在最小正数m ,满足当∣i-j∣≥m 时,aij =0,这时称w=2m-1为矩阵A的带宽。如图4.8(a)是一个w=3(m=2)的带状矩阵。带状矩阵也称为对角矩阵。由图4.8(a)可看出,在这种矩阵中,所有非零元素都集中在以主对角线为中心的带状区域中,即除了主对角线和它的上下方若干条对角线的元素外,所有其他元素都为零(或同一个常数c)。 带状矩阵A也可以采用压缩存储。一种压缩方法是将A压缩到一个n行w列的二维数组B中,如图4.8(b)所示,当某行非零元素的个数小于带宽w时,先存放非零元素后补零。那么aij可映射为b i′j′,映射关系为:
另一种压缩方法是将带状矩阵压缩到一维数组C中去,按以行优先的顺序存储其非零元素,如图4.8(c)所示,按其压缩规律找到相应的访问公式。另一种压缩方法是将带状矩阵压缩到一维数组C中去,按以行优先的顺序存储其非零元素,如图4.8(c)所示,按其压缩规律找到相应的访问公式。 如当w=3时,一维数组C以行优先存放带状矩阵A中的元素aij时,其访问公式为: 图4.8 带状矩阵及压缩存储
第三节 稀疏矩阵 什么是稀疏矩阵?如果矩阵中非零元素的个数远远小于矩阵元素的总数,这样的矩阵称为稀疏矩阵。 对于稀疏矩阵,只考虑非零元素的存储。但由于非零元素的分布没有规律,为了能找到相应的元素,仅存储非零元素的值是不够的,还要记下它所在的行和列。 下面讨论稀疏矩阵的两种压缩存储方法。 一、稀疏矩阵的三元组表存储 将矩阵中的非零元素所在行、列以及它的值构成一个三元组结构体(i,j,v)。这样一个三元组(i,j,v)便唯一地确定了矩阵中的一个非零元素,其中i和j分别表示非零元素的行号和列号,v表示非零元素的值。 将三元组按行优先,同一行中列号从小到大的规律排列,则可得到一个元素类型是三元组的线性表,称为三元组表。
三元组表是稀疏矩阵的一种顺序存储结构。稀疏矩阵的三元组存储的数据类型描述如下:三元组表是稀疏矩阵的一种顺序存储结构。稀疏矩阵的三元组存储的数据类型描述如下: #define MAXSIZE 10000 typedef int datatype; typedef struct{ int i,j; /*非零元素的行、列*/ datatype v; /*非零元素值*/ } triple; /*三元组类型*/ typedef struct{ triple data[MAXSIZE]; /*三元组表*/ int m,n,t; /*矩阵的行、列及非零元素的个数*/ } tripletable;
三元组存储结构因以行优先存放,存在以下的规律:元组中的第一列按行号的顺序由小到大排列,元组中的第二列是列号,列号在行号相同时也是由小到大排列。三元组存储结构因以行优先存放,存在以下的规律:元组中的第一列按行号的顺序由小到大排列,元组中的第二列是列号,列号在行号相同时也是由小到大排列。 图4.10 稀疏矩阵M的三元组结构a
二、稀疏矩阵的十字链表存储 三元组表可以看作是稀疏矩阵的顺序存储,这种表示方式适合求解稀疏矩阵的转置、相乘等运算。但当矩阵的非零元素的个数和位置在操作过程中变化较大时,如做一些操作(如加法、乘法)时,这种表示就十分不便了。为此,稀疏矩阵考虑采用链式存储结构来表示。 同以前链表表示相同,矩阵中的每个非零元素也用一个结点来表示,结点中除了表示非零元素的行、列、值(row,col,val)三个域外,还增加了两个指针域:向下域down用于链接同一列中的下一个非零元素;向右域right用于链接同一行中的下一个非零元素。其结点结构如图4.11(a)所示。 图4.11 十字链表的结点结构
这样一来,同一行的非零元素通过向右域right链接成一个线性链表,称为行链表;同一列的非零元素通过向下域down也链接成一个线性链表,称为列链表。矩阵中的每个非零元素既是某个行链表中的一个结点,又是某个列链表中的一个结点,整个矩阵构成了一个十字交叉的链表,这种存储结构称为十字链表。这样一来,同一行的非零元素通过向右域right链接成一个线性链表,称为行链表;同一列的非零元素通过向下域down也链接成一个线性链表,称为列链表。矩阵中的每个非零元素既是某个行链表中的一个结点,又是某个列链表中的一个结点,整个矩阵构成了一个十字交叉的链表,这种存储结构称为十字链表。 为了运算上的方便,给十字链表增加了一个表头结点,其结构与普通的存储非零元素的表头结点相同,如图4.11(b)所示。在实际应用中,可将表头结点的行、列域都设为零。由于行、列链表的表头结点的行列域均为零,而且都指向各自链表的第一个非零元素结点,所以行、列链表的表头结点可以合用。另外,表头结点的值域val没有使用,可以将其改造成指针域next,用于存放指向下一个表头结点的指针,并通过该指针域将所有的表头结点也链接成一个链表,在这个均是表头结点的链表中,再附加一个表头结点,作为“总”的表头结点,其指针域next指向第一个表头结点,结构和表头结点一样,其中值域row和col分别表示矩阵的行数和列数,另外两个指针域闲置。
例如,稀疏矩阵A如图4.12所示。 图4.12 稀疏矩阵A 可以用如图4.13所示的十字链表表示。 用十字链表表示稀疏矩阵的结构特点如下:
(1) 稀疏矩阵的每一行与每一列均用带表头结点的循环链表表示; (2) 表头结点中的行域与列域的值均置为0(即row=0,col=0); (3) 行、列链表的表头结点合用,且这些表头结点通过值域(即val)相链接,并增加一个结点作为它们的表头结点H,其行、列域值分别存放稀疏矩阵的行数与列数。 由此可以看出,只要给出头指针H的值,便可以扫描到稀疏矩阵中的任意一个非零元素。 三、稀疏矩阵的转置运算 矩阵的转置运算就是按一定规律变换元素的位置,即把位于(i,j)的元素换到(j,i)位置上。对于一个m×n的矩阵M,它的转置矩阵是一个n×m的矩阵N,且Mi,j=Nj,i,
其中,1≤i≤n,1≤j≤m。矩阵转置就是把矩阵元素的行和其中,1≤i≤n,1≤j≤m。矩阵转置就是把矩阵元素的行和 列对换。例如,图4.14的稀疏矩阵M的转置矩阵为图4.15 所示的稀疏矩阵N。 图4.14稀疏矩阵M 图 4.15稀疏矩阵M的转置矩阵N
将稀疏矩阵M和M矩阵转置后得到的稀疏矩阵N分别用三元组表存储,其结构示意图如图4.16、图4.17所示。将稀疏矩阵M和M矩阵转置后得到的稀疏矩阵N分别用三元组表存储,其结构示意图如图4.16、图4.17所示。 图4.16 稀疏矩阵M的三元组表结构 图4.17 转置矩阵N的三元组表结构
a.data和b.data都具有上述三元组存放的规律,这是矩阵转置算法实现的依据。 算法思路: (1)矩阵M的行、列转化成矩阵N的列、行; (2)在a.data中依次找第一列的、第二列的,直到最后一列,并将找到的每个三元组的行、列交换后,按行号从小到大的顺序存储到b.data中即可。 用C语言描述稀疏矩阵的转置算法如下: void transpose (SPMATRIX b, SPMATRIX a) { int p,q,col b.m=a.n; b.n=a.m; b.t=a.t; if (a.t0) { q=1;
for (col=1;col<=a.n; col++) for (p=1; p<=a.t; p++) if (a.data[p].j==col) { b.data[q].j=a.data[p].i; b.data[q].i=a.data[p].j; b.data[q].v=a.data[p].v; q++; } } }
本章小结 本章主要介绍的内容如下: 多维数组在计算机中有两种存放方式:行优先和列优先。 三种特殊矩阵:对称矩阵、三角矩阵和带状矩阵。 对称矩阵关于主对角线对称。为了节省存储单元,可以进行压缩存储,对角线以上的元素和对角线以下的元素可以共用存储单元,故n×n的对称矩阵只需n*(n+1)/2个存储单元即可。 三角矩阵有上三角矩阵和下三角矩阵之分,进行压缩存储时,n×n的三角矩阵只需n*(n+1)/2+1个存储单元即可。 带状矩阵也称为对角矩阵。一种压缩存储方法是将其压缩到一个n行w列的二维数组B中,另一种压缩存储方法是将带状矩阵压缩到向量中去,按以行优先,顺序地存储其非零元素,按其压缩规律,找到相应的访问公式。 稀疏矩阵的非零元素排列无任何规律,进行压缩存储时,可以采用三元组表示法或十字链表表示法。
实 训 一、实训目的 1.掌握稀疏矩阵的表示方法及其运算的实现。 2.实现稀疏矩阵在三元组表、十字链表等表示下的各种运算。 二、实训内容 1.问题描述: 稀疏矩阵是指那些多数元素为零的矩阵。利用“稀疏”特点进行存储和计算可以大大节省存储空间,提高计算效率。实现一个能进行稀疏矩阵基本运算的运算器。 2.基本要求: 以三元组顺序表表示稀疏矩阵,实现矩阵转置的运算。稀疏矩阵的输入形式采用三元组表示,而运算结果的矩阵则以通常的阵列形式列出。
3.测试数据: 三、实训过程 1.算法分析: (1)首先应输入矩阵的行数和列数,并判别给出的两个矩阵的行、列数对于所要求作的运算是否相匹配。可设矩阵的行数和列数均不超过20。 (2)程序可以对三元组的输入顺序加以限制,例如,按行优先。 (3)在用三元组表示稀疏矩阵时,相加或相减所得结果矩阵应该另生成,乘积矩阵也可用二维数组存放。
2.算法提示: #define MAXSIZE 100 struct node { int i,j; /*定义三元组的行、列号*/ int v; /*三元组的值*/ }; struct sparmatrix { int rows,cols; / *稀疏矩阵的行、列数*/ int terms; /*稀疏矩阵的非零元个数*/ struct node data[MAXSIZE]; /*存放稀疏矩阵的三元组表*/ }; void transpose(struct sparmatrix a) /*调用转置算法*/ 四、实训总结 通过本实训的实际操作,使读者掌握如何定义稀疏矩阵的三元组存储结构,在此基础上实现稀疏矩阵的初始化、取稀疏矩阵元素等基本操作,并最终利用它们来解决实际问题。
思考与习题 一、填空题 1.一维数组的逻辑结构是( ),存储结构是( );对于二维数组或多维数组,可分为( )和( )两种不同的存储方式。 2.对于一个二维数组A[m][n],若按以行优先存储,则任一元素aij相对于a00的地址为( )。 3.三维数组A[c1..d1,c2..d2,c3..d3]共含有( )个元素。 4.数组A[1..10,-2..6,2..8]以行优先的顺序存储,设第一个元素的首地址是100,每个元素占3个存储单元的存储空间,则元素a507的存储地址为( )。 二、选择题 1.二维数组M的成员是4个字符(每个字符占一个存储单元)组成的字符串,行下标i的范围从0到8,列下标j的范围从0到5,则存放M至少需要()个字节。 A.90 B.360 C.216 D.540
2.二维数组M的元素是4个字符(每个字符占一个存储单元)组成的字符串,行下标i的范围从0到4,列下标j的范围从0到5,M按行存储时元素m35的起始地址与M按列存储时元素( )的起始地址相同。 A.m43 B.m34 C.m35 D.m44 3.稀疏矩阵一般的压缩存储方法有两种,即()。 A.二维数组和三维数组 B. 三元组表和散列 C.三元组表和十字链表 D. 散列和十字链表 三、应用题 1.有数组A[4][4],把1到16个整数分别按顺序放入a00…a03,a10…a13,a20…a23,a30…a33中,编写一个函数获取数据,并求出两条对角线元素的乘积。 2.试写一个算法,查找十字链表中某一个非零元素x。 3.给定矩阵S如下,写出它的三元组表和十字链表。