1、课前导学 5.1 数组的定义 5.2 数组顺序存储的表示和实现 5.3 矩阵的压缩存储 5.4 广义表的定义 5.5 广义表的存储结构,第五章 数组和广义表,【学习目标】,理解数组类型的特点, 掌握数组在“以行为主”的存储表示中的地址计算方法; 掌握特殊矩阵的存储压缩表示方法; 掌握广义表的结构特点及其存储表示方法。,【重点和难点】,数组类型的定义及存储位置计算【知识点】数组、特殊矩阵、压缩存储、广义表,【课前思考】,为什么顺序表以及其它线性结构的顺序存储结构都可以用“一维数组“来描述?,因为在高级编程语言中实现的一维数组正是用的这种顺序存储的映象方式。,5.1 数组的定义,1. 基本概念 数
2、组:按一定格式排列起来的一列同一属性的项目,是相同类型的数据元素的集合。有一维数组A5、二维数组A55、三维数组A555、多维数组等。二维数组:每一行都是一个线性表,每一个数据元素既在一个行表中,又在一个列表中。,数组的逻辑结构:一维数组是线性结构。多维数组属于非线性结构。但数组元素的下标一般具有固定的下界和上界,因此它比其他复杂的非线性结构简单。,2.数组的抽象数据类型,ADT Array 数据对象:Daj1j2jn | ji =0,.,bi-1, i=1,2,n, n(0)称为数组的维数,bi是数组第i维的长度,ji是数组元素的第i维下标,aj1j2jnElemSet 数据关系:RR1,
3、R2, ., Rn Ri | 0 jk bk -1, 1 k n 且k i, 0 ji bi -2, aj1jijn , aj1ji+1jnD, i=2,.,n ,基本操作:,InitArray(&A, n, bound1, ., boundn) 操作结果:若维数n和各维长度合法,则构造相应的数组A,并返回OK。 DestroyArray(&A) 操作结果:销毁数组A。 Value(A, &e, index1, ., indexn) 初始条件:A是n维数组,e为元素变量,随后是n个下标值。 操作结果:若各下标不超界,则e赋值为所指定的A的元素值,并返回OK。 Assign(&A, e, ind
4、ex1, ., indexn) 初始条件:A是n维数组,e为元素变量,随后是n个下标值。 操作结果:若下标不超界,则将e的值赋给所指定的A的元素,并返回OK。 ADT Array,5.2 数组顺序存储的表示和实现,数组的存储结构:由于数组一旦建立,其维数和维界就确定了,则结构中的数据元素个数和元素之间的关系就不再发生变动,故一般采用顺序存储结构。数组是一种随机存储结构。,由于计算机中的存储单元是一维结构,数组是多维结构,用一维的连续单元存放数组时,按存放次序的不同有下列不同的存放形式。按行优先存放 按列优先存放,数组元素存储位置的计算公式, 按行优先顺序存放(以二维数组、下标从0开始为例) 元
5、素aij的存储地址为: Loc(aij)= Loc(a00)+(I* n +j)L (0im, 0jn) (式中:Loc(a00)为元素a00的存储位置,即二维数组的起始存储位置,也称基地址,m为二维数组的总行数,n为总列数,aij代表其中第i行、第j列的那个元素,每个数据元素占L个存储单元) 按列优先顺序存放 元素aij的存储地址为: Loc(aij)= Loc(a00)+(j* m +i)L (0im, 0jn),三维数组的计算,多维数组,各维元素个数为 m1, m2, m3, , mn下标为 i1, i2, i3, , in 的数组元素的存储地址:,LOC ( i1, i2, , in
6、) = a + ( i1*m2*m3*mn + i2*m3*m4*mn+ + in-1*mn + in ) * l,数组顺序存储的表示和实现,#include / 标准头文件,提供宏va_start、va_arg 和va_end,用于存取变长参数表 #define MAX_ARRAY_DIM 8 / 假设数组维数的最大值为8 typedef struct ElemType *base; /数组元素的基址,由InitArray分配 Int dim; /数组维数 Int *bounds; /数组维界基址,由InitArray分配 Int * constants; /数组映象函数常量基址,由Init
7、Array分配 Array;,数组的顺序存储结构图示,Array,3,a231 . .a011 a010 a001 a000,8 2 1,3 4 2,A342数组的存储结构,Constants的计算: A.Constantsdim-1=1; For( i=dim-2;i=0;-i) A.Constantsi=A.boundsi+1* A.Constantsi+1;,例: 计算多维数组的地址,已知4维数组: A3456, 求A1241的地址 A1241=A0000+1*4*5*6+2*5*6+4*6+1 A.Constants3=1 A.Constants2=6 A.Constants1=5*6
8、 A.Constants0=4*5*6,基本操作,(1)初始化 Status InitArray (Array /ap为va_list类型,是存放变长参数表信息的数组,for (I=0; I=0;-i) A.constantsi= A.boundsi+1+ A.constantsi+1 return OK;,(2)销毁数组,Status DestroyArray (Array ,(3)求元素地址,Status Locate (Array A,va_list ap, int ,(4)给数组赋值,Status Assign (Array ,5.3 矩阵的压缩存储,1. 基本概念稀疏矩阵(Spars
9、eMatrix):是矩阵中的一种特殊情况,其非零元素的个数远小于零元素的个数。 设m行n列的矩阵含t个非零元素,则称,以二维数组表示高阶的稀疏矩阵时,会产生零值元素占的空间很大且进行了很多和零值的运算的问题。,特殊矩阵:值相同的元素或0元素在矩阵中的分布有一定的规律。如下三角阵、上三角阵。压缩存储:为多个值相同的元素只分配一个存储空间;对0元素不分配空间。目的是节省大量存储空间。如:n x n的矩阵一般需要n2个存储单元,当为对称矩阵时需要n(1+n)/2个单元。,2. 特殊矩阵的存储 1)对称矩阵若n阶矩阵中的元满足性质aij=aji , 则称为对称矩阵。如:,2)三角矩阵下(上)三角矩阵是
10、指矩阵的上(下)三角中的元均为常数或零的n阶矩阵。如:,3)对角矩阵,Loc(aij)=Loc(a11)+2(i-1)+(j-1),M由(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)唯一确定,3 稀疏矩阵的压缩存储方法 稀疏矩阵:非零元较零元少,且分布没有一定规律的矩阵 压缩存储原则:只存矩阵的行列维数和每个非零元的行列下标及其值,当矩阵中的非0元 素少于1/3时即可节省 存储空间。,(1)三元组顺序表即用顺序存储结构表示三元组表,三元组表所需存储单元个数为3(t+
11、1) , 其中t为非零元个数,6 7 8,1 2 12,1 3 9,3 1 -3,3 6 14,4 3 24,5 2 18,6 1 15,6 4 -7,ma,i j v,0 1 2 3 4 5 6 7 8,ma0.i,ma0.j,ma0.v分别存放 矩阵行列维数和非零元个数,稀疏矩阵的三元组顺序表存储表示,#define MAXSIZE 100 /* 非零元个数的最大值 */typedef structint i,j; /* 行下标,列下标 */ElemType e; /* 非零元素值 */Triple;typedef structTriple dataMAXSIZE+1; /* 非零元三元组
12、表,data0未用 */int mu,nu, tu; /* 矩阵的行数、列数和非零元个数 */TSMatrix;,应用实例求转置矩阵 问题描述:已知一个稀疏矩阵的三元组表,求该矩阵转置矩阵的三元组表 问题分析:一般矩阵转置算法:逐行逐个扫描,进行互换 解决思路:将矩阵行、列维数互换将每个三元组中的i和j相互调换重排三元组次序,使mb中元素以N的行(M的列)为主序,方法一:按M的列序转置即按mb中三元组次序依次在ma中找到相应的三元组进行转置。为找到M中每一列所有非零元素,需对其三元组表ma从第一行起扫描一遍。由于ma中以M行序为主序,所以由此得到的恰是mb中应有的顺序。,算法分析:T(n)=O
13、(M的列数n非零元个数t)若 t 与mn同数量级,则 此算法仅适合tm x n 的情况。,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,Status TransposeSMatrix ( TSMatrix M, TSMatrix / TransposeSMatrix,方法二:快速转置 即按ma中三元组次序转置,转置结果放入mb中恰当位置 此法关键是要预先确定M中每一列第一个非零元在mb中位置,为确定这些位置,转置前应先求得M的每一列中非零元个数,实现:设两个数组 numcol:表示矩阵M中第co
14、l列中非零元个数 cpotcol:指示M中第col列第一个非零元在mb中位置 显然有:,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,快速转置算法描述:,void FastTransposeSMatrix(TSMatrix M, TSMatrix ,/求第col列中第一个非零元素在b.data中的序号 for (col=2; col=M. nu; +col) cpotcol = cpotcol-1 + numcol-1; / 求T中每一行的第一个非零元在T.dat
15、a中的序号 for (p=1; p=M.tu; +p) / 转置矩阵元素 col = M.datap.j; q = cpotcol; T.dataq.i =M.datap.j; T.dataq.j =M.datap.i; T.dataq.e =M.datap.e; +cpotcol; / for / if / FastTransposeSMatrix,算法分析:T(n)=O(M的列数n+非零元个数t)若 t 与mn同数量级,则T(n)=O(mn),(2)行逻辑链接的顺序表稀疏矩阵中为了随机存取任意一行的非0元素,需要知道每一行的第一个非0元素在三元组表中的位置,因此将上述快速转置算法中指示行信
16、息的辅助数组cpot固定在稀疏矩阵的存储结构中,让每一行对应一个单链表,每个单链表都有一个表头指针,这种“带行链接信息”的三元组表即称为行逻辑链接的顺序表。,行逻辑联接的顺序表的表示法 #define MAXMN 500 / 假设矩阵行数和列数的最大值为500 typedef struct Triple dataMAXSIZE + 1; / 非零元三元组表,data0未用 int rposMAXMN + 1; / 指示各行第一个非零元的位置 int mu, nu, tu; / 矩阵的行数、列数和非零元个数 RLSMatrix; / 行逻辑链接顺序表类型,mu nu tu,i j e ,rpos
17、.,空,有值,RLSMatrix,0 1 tu tu+1 MAXSIZE,行逻辑联接的顺序表图示,0 1 mu mu+1 MAXRC,空,有值,空,空,(3)十字链表当矩阵的非0元个数和位置在操作过程中变化较大时,就不宜采用顺序存储结构来表示三元组的线性表,如矩阵相加,这时应该采用链式存储结构,设行指针数组和列指针数组,分别指向每行、列第一个非零元,构成十字链表。,稀疏矩阵的十字链表存储表示,typedef struct OLNode /*结点的定义*/int i,j; /* 该非零元的行和列下标 */ElemType e; /* 非零元素值 */struct OLNode *right,*d
18、own; /* 该非零元所在行表和列表的后继链域 */OLNode,*OLink;typedef struct /*链表的定义*/OLink *rhead,*chead; /* 行和列链表头指针向量基址,由CreatSMatrix_OL()分配 */int mu,nu,tu; /* 稀疏矩阵的行数、列数和非零元个数 */CrossList;,OLink,OLNode,CrossList,3 4 4,OLink,rhead chead mu nu tu,rhead chead mu nu tu,NULL,NULL,NULL,NULL,NULL,NULL,NULL,5.4 广义表,1. 广义表(g
19、eneral list)的基本概念 n ( 0 )个表元素组成的有限序列。 广义表是递归定义的线性结构,是一个多层次的线性结构,是线性表的推广。记作 LS = (a0, a1, a2, , an-1) LS是表名,ai是表元素,它可以是表 (称为子表),可以是数据元素(称为原子)。n为表的长度。n = 0 的广义表为空表。n 0时,表的第一个表元素称为广义表的表头(head),除此之外,其它表元素组成的表称为广义表的表尾(tail)。,广义表LS = (a1,a 2, ,an )的结构特点,1) 广义表中的数据元素有相对次序; 2) 广义表的长度定义为最外层包含的元素个数; 3) 广义表的深度
20、定义为所含括弧的重数;注意: “原子”的深度为“0”,长度为1;“空表”的深度为1,长度为0。 例: C=(a,(b,(c,d))的长度和深度分别是多少?列表C的长度是2,两个元素分别是原子a和子表(b,(c,d)),深度是34) 广义表可以共享; 5) 广义表可以是一个递归的表; 递归表的深度是无穷值,长度是有限值。如: E=(a,E),6) 任何一个非空广义表LS = ( a1, a2, , an) 均可分解为两部分: 表头 GetHead(LS) = a1 和 表尾 GetTail(LS) = ( a2, ,an) 例如: D = (E,F) E = (a,(b,c) F = (d,(e
21、) A=()LS= (A,D)= ( ),(E,F)= ( ),(a,(b,c),F) GetHead(LS) = A GetTail(LS) = ( D ) GetHead( D ) = E GetTail( D ) = ( F ) GetHead( E ) = a GetTail( E ) = (b,c)GetHead(b, c)=(b, c) GetTail(b, c)=( )GetHead( (b, c)=b GetTail( ( b, c)=(c)GetHead( ( c )=c GetTail( (c)= ( ),2. 广义表的抽象数据类型定义,ADT Glist 数据对象:Dei
22、 | i=1,2,n; n0; eiAtomSet 或 eiGList,AtomSet为某个数据对象 数据关系:LR| ei-1 ,ei D, 2in 基本操作: 结构的创建和销毁 InitGList(,状态函数 GListLength(L); GListDepth(L); GListEmpty(L); GetHead(L); GetTail(L); 插入和删除操作 InsertFirst_GL( ADT GList,5.5 广义表的存储结构,由于广义表是递归定义的,其中的数据元素可以具有不同的结构,难以用顺序存储结构表示,故常采用链式存储结构,每个数据元素用一个结点表示。 1. 广义表的头尾
23、链表存储结构 一个表结点可由三个域组成:标志域、指示表头的指针域和指示表尾的指针域。 一个原子结点只需两个域:标志域和值域。,广义表的头尾链表存储表示,typedef enumATOM, LIST ElemTag; /* ATOM=0:原子,LIST=1:子表 */typedef struct GLNodeElemTag tag; /* 公共部分,用于区分原子结点和表结点 */union /* 原子结点和表结点的联合部分 */AtomType atom; /* atom是原子结点的值域,AtomType由用户定义 */struct struct GLNode *hp,*tp; ptr; /*
24、ptr是表结点的指针域,prt.hp和ptr.tp分别指向表头和表尾 */a;*GList,GLNode; /* 广义表类型 */,2. 广义表的扩展线性链表存储结构,广义表的扩展线性链表存储表示,typedef enumATOM, LISTElemTag; / ATOM=0:原子,LIST=1:子表typedef struct GLNodeElemTag tag; / 公共部分,用于区分原子结点和表结点 union / 原子结点和表结点的联合部分AtomType atom; / 原子结点的值域 struct GLNode *hp; / 表结点的表头指针a;struct GLNode *tp;
25、 / 相当于线性链表的next,指向下一个元素结点 *GList,GLNode; / 广义表类型GList是一种扩展的线性链表,3. 广义表的运算,(1) 求广义表的长度在广义表中,同一层次的每个结点是通过next域链接起来的,可以看成一个单链表,求广义表的深度就是求单链表的深度。用递归算法求,若单链表非空,其长度等于1+表头结点的后继单链表的长度;若为空,则长度=0。,int GListLength(GList L)/求广义表L的长度,即元素个数int len=0;GList p;if(L-tag=LIST,(2) 求广义表的深度,广义表深度等于其所有子表中表的最大深度+1,若一个表为空或仅
26、由单元素所组成,则深度为1。 用递归调用求子表深度。 int GListDepth (GList L) / 采用头尾链表存储结构,求广义表的深度if (!L) return 1; / 空表深度为1if (L-tag=ATOM) return 0; / 单原子表深度为0for(max=0, pp=L; pp; pp=pp-ptr.tp)dep=GListDepth (pp-ptr.hp); / 求以pp-ptr.hp为头指针的子表深度if(depmax) max=dep;return max+1; / 非空表的深度是各元素的深度的最大值加1,(3) 建立广义表的存储结构建立广义表的存储结构也是一个递归算法,首先建立表头指针GL,然后根据所输入的字符(左括号、逗号、字母、#、右括号、分号)确定属于表元素、单元素、空表还是结束标志,然后确定是否建立一个子表的表头指针。 (4) 打印输出广义表也是一个递归调用的过程。,小结,数组的定义与存储 特殊矩阵的压缩存储 广义表的定义、存储结构,