1、第五章 数组和广义表,本章主要介绍下列内容,数组的定义 数组的顺序表示与实现 矩阵的压缩存储 广义表的定义 广义表的存储结构,第五章 数组和广义表,本章学习要求 理解数组的逻辑结构和存储方式; 掌握数组在以行序为主的存储结构中的地址计算方法。 理解矩阵和稀疏矩阵的压缩存储方法; 掌握特殊矩阵实现压缩存储时的下标变换。 理解稀疏矩阵的两种存储方式:三元组表和十字链表; 掌握稀疏矩阵运算方法; 了解广义表的定义; 理解存储结构及其取表头、表尾操作。,5.1 数组 5.2 特殊矩阵的压缩存储 5.3 广义表,5.1.1 数组的定义和基本运算数组是一种特殊的线性表,其特殊在于每个数据元素可以又是一个线
2、性表结构。因此,数组结构可以简单地定义为:若线性表中的数据元素为非结构的简单元素,则称为一维数组,即为向量;若一维数组中的数据元素又是一维数组结构,则称为二维数组;依次类推,若二维数组中的元素又是一个一维数组结构,则称作三维数组。结论:线性表结构是数组结构的一个特例,而数组结构又是线性表结构的扩展。举例:,5.1 数组,其中,A是数组结构的名称,整个数组元素可以看成是由m个行向量和n个列向量组成,其元素总数为mn。在C语言中,二维数组中的数据元素可以表示成a表达式1表达式2,表达式1和表达式2被称为下标表达式,比如,aij。,数组结构在创建时就确定了组成该结构的行向量数目和列向量数目,因此,在
3、数组结构中不存在插入、删除元素的操作。数组结构的基本操作: 取值操作:给定一组下标,存取相应的数据元素 赋值操作:给定一组下标,修改数据元素的值,5.1.2 数组的存储结构从理论上讲,数组结构也可以使用两种存储结构,即顺序存储结构和链式存储结构。然而,由于数组结构没有插入、删除元素的操作,所以使用顺序存储结构更为适宜。换句话说,一般的数组结构不使用链式存储结构。组成数组结构的元素可以是多维的,但存储数据元素的内存单元地址是一维的,因此,在存储数组结构之前,需要解决将多维关系映射到一维关系的问题。,次序约定 以行序为主序:将数组元素按行向量排列,第i+1个行向量紧接在第i个行向量后面。 以列序为
4、主序:将数组元素按列向量排列,第i+1个列向量紧接在第i个列向量后面。,数组元素的地址计算公式: 1、按行优先顺序存储的二维数组Amn地址计算公式LOC(aij)=LOC(a00)+in+jd 其中: LOC(a00)是开始结点的存放地址(即基地址) d为每个元素所占的存储单元数 由地址计算公式可得,数组中任一元素可通过地址公式在相同时间内存取。即顺序存储的数组是随机存取结构。 2、按列优先顺序存储的二维数组Amn地址计算公式 LOC(aij)=LOC(a00)+jm+id,5.2 特殊矩阵的压缩存储,矩阵是在很多科学与工程计算中遇到的数学模型。在数学上,矩阵是这样定义的:它是一个由sn个元素
5、排成的s行(横向)n列(纵向)的表。下面就是一个矩阵:,1、矩阵的二维数组描述 矩阵用二维数组描述时,存储的密度为1。可以对其元素进行随机存取,各种矩阵运算也非常简单。,2、矩阵的压缩存储 矩阵中非零元素呈某种规律分布或者矩阵中出现大量的零元素的情况下,为了节省存储空间,我们可以对这类矩阵进行压缩存储:即为多个相同的非零元素只分配一个存储空间;对零元素不分配空间。,特殊矩阵,所谓特殊矩阵是指非零元素或零元素的分布有一定规律的矩阵。常见的有对称矩阵、三角矩阵和对角矩阵等。,对称矩阵 1对称矩阵:在一个n阶方阵A中,若元素满足下述性质: aij=aji 0i,jn-1 则称A为对称矩阵。,对称矩阵
6、,2. 对称矩阵的压缩存储 对称矩阵中的元素关于主对角线对称,故只要存储矩阵中上三 角或下三角中的元素,让每两个对称的元素共享一个存储空间。 这样,能节约近一半的存储空间.,按“行优先顺序“存储主对角线(包括对角线)以下的元素,即按a00,a10,a11,,an-1,0, an-1,1,an-1,n-1次序存放在一 个向量sa0n(n+1)2-1中 (下三角矩阵中,元素总数为 n(n+1)2)。 其中:sa0= a00 , sa1 = a10 , , san(n+1)2-1= an-1,n-1,对称矩阵,元素aij的存放位置 aij元素前有i行(从第0行到第i-1行),一共有: 1+2+i=i
7、(i+1)2个元素; 在第i行上,aij之前恰有j个元素(即ai0,ail,ai,j-1), 因此有: sai(i+1)2+j= aij,aij和sak之间的对应关系: 若ij,k=i(i+1)2+j 0kn(n+1)2 若ij,k=j(j+1)2+i 0kn(n+1)2 令I=max(i,j),J=min(i,j),则k和I,J的对应关系 可统一为: k=I(I+1)2+J 0kn(n+1)2,对称矩阵,3.对称矩阵的地址计算公式 LOC(aij)=LOC(sak) =LOC(sa0)+kd=LOC(sa0)+I(I+1)2+Jd 通过下标变换公式,能立即找到矩阵元素aij在其压缩存储 表示
8、sa中的对应位置k。因此是随机存取结构。 【例】a21和a12均存储在sa4中,这是因为 k=I(I+1)2+J=2(2+1)2+1=4,三角矩阵,1.三角矩阵的划分:以主对角线划分,三角矩阵有上三角矩阵和下三角矩阵两种。,上三角矩阵: 它的下三角(不包括主角线)中的元素均为常数c。,下三角矩阵: 与上三角矩阵相反,它的主对角线上方均为常数c,,三角矩阵,2.三角矩阵的压缩存储: 三角矩阵中的重复元素c可共享一个存储空间,其余的元素 正好有n(n+1)2个,因此,三角矩阵可压缩存储到向量 sa0n(n+1)2中,其中c存放在向量的最后一个分量中。, 上三角矩阵中aij和sak之间的对应关系 上
9、三角矩阵中,主对角线之上的第p行(0pn)恰有n-p 个元素,按行优先顺序存放上三角矩阵中的元素aij时: aij元素前有i行(从第0行到第i-1行),一共有: (n-0)+(n-1)+(n-2)+(n-i+1)=i(2n-i+1)2个元素; 在第i行上,aij之前恰有j-i个元素(即aij,ai,j+l,ai,j-1), 因此有: sai(2n-i+1)2+j-i= aij 所以: i(2n-i+1)2+j-i 当ij k= n(n+1)/2 当ij,下三角矩阵中aij和sak之间的对应关系 i(i+1)2+j 当ij k= n(n+1)/2 当ij 注意: 三角矩阵的压缩存储结构是随机存取
10、结构。,对角矩阵,对角矩阵:所有的非零元素集中在以主对角线为中心的带状区 域中,即除了主对角线和主对角线相邻两侧的若干条对角线上 的元素之外,其余元素皆为零的矩阵为对角矩阵。,其中: 非零元素仅出现在主对角上(aii,0in-1),紧邻主对角线 上面的那条对角线上(ai,i+1 ,0in-2)和紧邻主对角线下面的 那条对角线上(a i+1,i,0in-2)。当|i-j|1时,元素aij=0。 由此可知,一个k对角线矩阵(k为奇数)A是满足下述条件的矩阵: 若|i-j|(k-1)2,则元素aij=0。 对角矩阵可按行优先顺序或对角线的顺序,将其压缩存储到 一个向量中,并且也能找到每个非零元素和向
11、量下标的对应关系。,稀疏矩阵,设矩阵Amn中有s个非零元素,若s远远小于矩阵元素的总数(即smn),则称A为稀疏矩阵。,稀疏矩阵的压缩存储:为了节省存储单元,可只存储非 零元素。由于非零元素的分布一般是没有规律的,因此在存 储非零元素的同时,还必须存储非零元素所在的行号、列号,才能迅速确定一个非零元素是矩阵中的哪一个元素。稀疏矩阵的压缩存储会失去随机存取功能。 其中每一个非零元素所在的行号、列号和值组成一个三 元组(i,j,aij),并由此三元组惟一确定。 稀疏矩阵进行压缩存储通常有两类方法:顺序存储和链式存储。链式存储方法【参见其它参考书目】。,1、稀疏矩阵三元组表存储 将表示稀疏矩阵的非零
12、元素的三元组按行优先(或列优先) 的顺序排列(跳过零元素),并依次存放在向量中,这种稀疏矩 阵的顺序存储结构称为三元组表。,例如,下列三元组表 (1,2,12)(1,3,9),(3,1,- 3),(3,6,14),(4,3,24), (5,2,18),(6,1,15),(6,4,-7)加上(6,7)这一对行、列值便可作为下列矩阵M的另一种描述。,#define SMAX 1024 typedef struct node int i,j;datatype v; SPNode; typedef structint mu,nu,tu;SPNode dataSMAX;SPMatrix;,将三元组按行优
13、先,同一行中列号从小到大的规律排列成一个线性表,称为三元组表。,2 稀疏矩阵的三元组表类型定义,1 2 12,1 3 9,3 1 -3,3 6 14,4 3 24,5 2 18,6 1 15,6 4 -7,求转置矩阵 问题描述:已知一个稀疏矩阵的三元组表,求该矩阵转置矩阵的三元组表 问题分析 一般矩阵转置算法:,for(col=0;coln;col+)for(row=0;rowm;row+)ncolrow=mrowcol; T(n)=O(mn),3 稀疏矩阵的三元组表应用举例,解决思路:只要做到将矩阵行、列维数互换将每个三元组中的i和j相互调换重排三元组次序,使B三元组中元素以B的行(A的列)
14、为主序,方法一:按A的列序转置,for (col=1;colnu;col+)for(p=1;ptu;p+)if(找到第col列) 赋值给B;,7 6 8,1 3 -3,1 6 15,2 1 12,2 5 18,3 1 9,3 4 24,4 6 -7,6 3 14,col=1,col=2,算法描述:见P117算法5-1,算法分析:T(n)=O(A的列数n非零元个数t)若 t 与mn同数量级,则,方法二:快速转置 即按A中三元组次序转置,转置结果放入B中三元组恰当位置,实现:设两个数组 numcol:表示矩阵A中第col列中非零元个数 cpotcol:指示A中第col列第一个非零元在mb中位置 显
15、然有:,1,3,5,7,8,8,9,7 6 8,1 3 -3,1 6 15,2 1 12,2 5 18,3 1 9,3 4 24,4 6 -7,6 3 14,4,6,2,9,7,5,3,算法描述:,1、B-nu=A-mu; B-mu=A-nu;B-tu= A-tu; 2、求num、cpot数组值; 3、for(i=1;itu;i+)计算该元素在B三元组表中位置k;B-datak=A-datai;,#define SMAX 1024 typedef struct node int i,j;datatype v; SPNode; typedef structint mu,nu,tu;SPNode
16、dataSMAX;SPMatrix;,见P118算法5-2,4、稀疏矩阵的十字链表存储 三元组表是用顺序方法来存储稀疏矩阵中的非零元素,当非零元素的位置或个数经常变化时,三元组表就不适合做稀疏矩阵的存储结构。元素的插入和删除会导致大量的结点移动。此时,采用链式存储结构更为合适。一般采用十字链表的链接存储方法。在该方法中,稀疏矩阵的每个非零元素可用一个含5个域的结点表示,结点结构信息如图5.5(a)所示,除了表示非零元素所在的行、列和值的三元组(i,j,v)外,还增加了两个链域:指向本行中下一个非零元素行指针域right和指向本列下一个非零元素列指针域down。同一行的非零元素通过right域链
17、接成一个线性链表,同一列的非零元素通过down域链接成一个线性表,每个非零元素既是某个行链表中的一个结点,又是某个列链表中的一个结点,整个矩阵构成了一个十字交叉的链表,故称这样的链表为十字链表。,结点定义,typedef struct node int row,col;datatype v;struct node *down, *right; MNode;,typedef struct node int row,col; struct node *down, *right;union v_nextdatatype v;struct node *next; Mnode,*MLink;,建立十字链
18、表算法,(1)输入行数、列数建立头结点HA; (2)建立行(列)头结点并形成循环链表; (3)输入t个三元组(i,j,a ij)申请结点并输入值;插入第i行链表;插入第j列链表; (4)返回HA;,5.3 广义表,广义表(Lists,又称列表)是线性表的推广。在第2章中,我们把线性表定义为n=0个元素a1,a2,a3,an的有限序列。线性表的元素仅限于原子项,原子是作为结构上不可分割的成分,它可以是一个数或一个结构,若放松对表元素的这种限制,容许它们具有其自身结构,这样就产生了广义表的概念。 1、广义表概念:广义表是n(n=0)个元素a1,a2,a3,an的有限序列,其中ai或者是原子项,或者
19、是一个广义表。通常记作LS=(a1,a2,a3,an)。LS是广义表的名字,n为它的长度。若ai是广义表,则称它为LS的子表。,2、广义表的表示通常用圆括号将广义表括起来,用逗号分隔其中的元素。为了区别原子和广义表,书写时用大写字母表示广义表,用小写字母表示原子。若广义表LS(n=1)非空,则a1是LS的表头,其余元素组成 的表(a2,a3,an)称为LS的表尾。,3、广义表的例子 显然广义表是递归定义的,这是因为在定义广 义表时又用到了广义表的概念。广义表的例子如下: (1)A=()A是一个空表,其长度为零。 (2)B=(e)表B只有一个原子e,B的长度为1。 (3)C=(a,(b,c,d)
20、表C的长度为2,两个元素分别为原子a和子表(b,c,d)。 (4)D=(A,B,C)表D的长度为3,三个元素都是广义表。显然,将子表的值代入后,则有 D=( ),(e),(a,(b,c,d)。 (5)E=(a,E)这是一个递归的表,它的长度为2, E相当于一个无限的广义表E=(a,(a,(a,(a,).,图5.7 广义表的图形表示,4、广义表的特征 从上述定义和例子可推出广义表的三个重要特征: (1)广义表的元素可以是子表,而子表的元素还可以是子表。由此,广义表是一个多层次的结构,可以用图形象地表示。P124 (2)广义表可为其它表所共享。例如在上述例(4)中,广义表A,B,C为D的子表,则在
21、D中可以不必列出子表的值,而是通过子表的名称来引用。 (3)广义表的递归性。综上所述,广义表不仅是线性表的推广,也是树的推广。,5、广义表的说明:由表头、表尾的定义可知:任何一个非空广义表其表 头可能是广义表,也可能不是广义表,而其表尾必定是 广义表。gethead(B)=e gettail(B)=( )gethead(D)=A gettail(D)=(B,C)由于(B,C)为非空广义表,则可继续分解得到:gethead(B,C)=B gettail(B,C)=(C)注意广义表( )和( ( ) )不同。前者是长度为0的空表,对其不能做求表头的和表尾的运算;而后者是长度为1 的非空表(只不过该
22、表中唯一的一个元素是空表)。对 其可进行分解,得到表头和表尾均为空表( )。,6 广义表的存储,由于广义表中的数据元素可以具有不同的结构,因此难以用顺序存储结构来表示,所以通常采用链式的存储结构来存储。,1、头尾表示法:表结点由三个域组成:标志域、指示表头的指针域和指示表尾的指针域;而原子域只需两个域:标志域和值域 2、孩子兄弟表示法:表结点由三个域组成:标志域、指示表头的指针域和指示表尾的指针域;原子结点的三个域为:标志域、值域和指示表尾的指针域。,在广义表中有两种数据元素,原子或广义表,因此,需要两种结构的结点:一种是表结点,一种是原子结点。按结点形式的不同,广义表的链式存储结构又可以分为
23、两种不同的存储方式:,其类型定义如下: typedef struct glnode int tag;unionint data;struct struct glnode *hp, *tp; ptr; *glist;,1、头尾表示法,例子:A=(),B=(e),C=(a,(b,c,d),D=(A,B,C),A=NULL,1、取广义表表头GetHead() GList GetHead(GList p) /*表空或无表头时返回null,否则返回表头指针*/if (!p | !p-tag) printf(“表空或广义表无表头”);return (NULL);return(p-ptr.hp); ,广义表
24、基本操作的实现,2、取广义表表尾GetTail() Glist GetTail(Glist p) /*空表或是单个原子返回null,否则返回表尾指针*/if (!p | !p-tag) printf(“空表或是单个原子”);return (NULL);return(p-ptr.tp); ,2 孩子兄弟表示法,指向孩子的指针,指向兄弟的指针,typedef enumATOM,LIST Elemtag; typedef struct GLENodeElemtag tag;uniondatatype data;struct GLENode *hp;ptr;struct GLENode *tp;*EG
25、List;,例子:A=(),B=(e),C=(a,(b,c,d),D=(A,B,C),例子:E=(a,E),F=(),本章小结,数组是由一个个数固定,类型相同的数据元素组成。每个元素在数组中的位置由它的下标决定。在数组的顺序存储中,数组分为以行为主序和以列为主序的存储方式。因此对于数组而言,一旦给定了它的维数和各维的长度,便可为它分配存储空间,并且可求出任何数组元素的存储地址。在特殊矩阵中,有些元素或者相同元素的分布有一定的规律。为节省空间,可对这些元素不分配存储单元或只分配一个存储单元。对三角矩阵、对称矩阵来说,可以用一维数组实现它们的压缩存储,以达到节省存储空间的目的。因此,实现特殊矩阵压
26、缩存储的关键是找出它们之间的变换对应关系。,稀疏矩阵是指值相同元素或者零元素分布没有一定规律的矩阵,其压缩存储可采用顺序结构存储非零元三元组和基于链式存储结构的十字链表方式。在压缩方式下,矩阵运算的算法的如何实现则是矩阵压缩存储重点讨论的问题。在本章中,着重介绍了在三元组表存储方式下的转置运算算法的实现方法。广义表是线性表的一种扩充,是数据元素的有限序列。理解广义表是一种具有递归特性的数据结构,其物理结构主要采用链式结构存储。在广义表的链式存储结构中有两种结点:用以表示广义表的表结点和用以表示原子元素的原子结点。广义表上的常用操作:也有查找、取元素、插入、删除、取表头、取表尾和求广义表的深度等操作。,