360 likes | 608 Views
5.1 数组的定义. 一、数组的定义 数组可看成是一种特殊的线性表,其特殊在于,线性表中的数据元素本身也是一个数据结构。 数组是由 下标和值 组成的序对的有限集合。数组中每组有定义的下标,都存在一个与其对应的值,这个值称为数组元素。即数组中的每个数据元素都对应一组下标 ( j 1 ,j 2 ,…,j n ) ,每个下标的取值范围是 0≤j i ≤b i-1 , b i 称为第 i 维的长度 ( i=1,2,…,n) 。 当 n=1 时, n 维数组就蜕化为定长的线性表; 反之, n 维数组可以看成是线性表的推广。.
E N D
5.1 数组的定义 一、数组的定义 数组可看成是一种特殊的线性表,其特殊在于,线性表中的数据元素本身也是一个数据结构。 数组是由下标和值组成的序对的有限集合。数组中每组有定义的下标,都存在一个与其对应的值,这个值称为数组元素。即数组中的每个数据元素都对应一组下标( j1,j2,…,jn),每个下标的取值范围是0≤ji≤bi-1,bi称为第i维的长度( i=1,2,…,n)。 当n=1时,n维数组就蜕化为定长的线性表; 反之,n维数组可以看成是线性表的推广。
( ) ( ) ( ) ( ) ( ) ( ) ( ) ( ) 例如,对于如下形式的m*n阶矩阵,可以用二维数组表示: Am×n= 二维数组A还可以看成一个线性表: A=(a0,a1,…,ap) (p=m-1或n-1) 其中,每个数据元素aj是一个列向量形式的线性表: aj=(a0,j,a1,j,…,am-1,j) (0 ≤j≤n-1) 或者每个数据元素是一个行向量形式的线性表: ai=(ai,0,ai,1,…,ai,n-1) (0 ≤i≤m-1)
二、数组的基本操作 1. 初始化 InitArray(A,n,bound1,…,boundn ) 如果维数n和各维长度合法,则构造相应数组A,并且返回1 2. 销毁 Destroyarray(A) 销毁已存在的数组A 3. 取值 Getarray(A,e,index1,…,indexn) 如果各下标不超界,则e被赋值为所指定的A的元素值,并且返回1 4. 赋值 Assign(A ,e, index1,…,indexn) 如果各下标不超界,则e赋值给所指定的A的元素值,并且返回1
5.2 数组的顺序存储结构 由于数组一般不作插入或删除操作,即一旦数组建立,结构中的数据元素个数和元素之间的关系就不再发生变动。因此数组采用顺序结构储存。 对于二维数组可以有两种顺序存储方式: 一种是以行序为主序的存储方式, 另一种是以列序为主序的存储方式。 在Basic,Pascal,C等语言中,用的是以行序为主序的存储结构,而在Fortran语言中,用的是以列序为主序的存储结构。
0 按行序为主序存放 1 n-1 n 2n-1 Loc( aij )= Loc( a00 ) + ( i×n + j )× L m*n-1
0 按列序为主序存放 1 m-1 m 2m-1 Loc( aij )= Loc( a00 ) + ( j×m + i )×L m*n-1
Loc( aij )= Loc( a00 ) + ( i×n + j )× L 676=644+(2×n + 2 ) ×1 n=15 Loc( a45 )= Loc( a00 ) + ( 4×n + 5 )× 1 = 709 同理,可以推出n维数组的数据元素存储位置的计算公式: Loc( aj1,j2,…,jn )= Loc( a0,0,…,0 ) + (b2×…×bn×j1 + b3×…×bn×j2 +…+bn×jn-1 + jn )× L =Loc( a0,0,…,0 )+( + jn ) × L 例如,假设有二维数组A[M,N],设A[0,0]在644处,A[2,2] 在676处。每个元素占一个存储空间,则A[4,5]的地址 为多少?以行序为主序存储。
5.3 矩阵的压缩存储 矩阵是许多科学与工程计算中经常遇到的研究对象。 在某些矩阵中,往往会出现许多值相同的元素或零元素,为了节省存储空间,可对这类矩阵进行压缩存储。压缩存储的原则是:对多个值相同的元素只分配一个存储空间,对零元不分配空间。 我们把相同的元素或零元素在矩阵中的有一定的规律分布称为特殊矩阵,如果矩阵中零元素占绝大部分的称为稀疏矩阵。
5.3.1 特殊矩阵的压缩存储 一、对称矩阵 在一个n阶方阵A中,若元素满足下述性质: aij=aji ,0≦i,j≦n-1, 则称A为对称矩阵。 对称矩阵中的元素关于主对角线对称,故只要存储矩阵中上三角或下三角中的元素,让每两个对称的元素共享一个存储空间,这样,能节约近一半的存储空间。 不失一般性,我们按“行优先顺序”存储主对角线(包括对角线)以下的元素。在这个下三角矩阵中,第i行恰有i+1个元素,元素总数为:n(n+1)/2.
a00 a01….… ….. a0,n-1 a10a11…….. ……. a1,n-1 …………………. an-1,0an-1,1…….. an-1,n-1 若i≧j,则ai j在下三角形中。 ai j之前的i行(从第0行到第i-1行)一共有1+2+…+i=i(i+1)/2个元素,在第i+1行上, ai j之前恰有j个元素(即ai0,ai1,…,ai,j-1),因此有: i(i+1)/2+j 0≦k≦ n(n+1)/2-1 若i<j,则aij是在上三角矩阵中。因为aij=aji,所以只要交换上述对应关系式中的i和j即可得到: j(j+1)/2+i 0≦ k ≦ n(n+1)/2-1
a00 0 0…….. 0 a10 a110…….. 0 …………………. 0 an-1,0an-1,1 an-1,2……an-1,n-1 二、三角阵 以主对角线划分,三角矩阵有上三角和下三角两种。上三角矩阵它的下三角(不包括主对角线)中的元素均为常数。下三角矩阵正好相反,它的主对角线上方均为常数。在大多数情况下,三角矩阵常数为零。 Loc(aij)=Loc(a00)+[i(i+1)/2+j]*L
a00a01 0…………… . 0 a10 a11 a120…………… 0 0a21 a22 a230……… 0 下标与k之间对应关系: 3i-1当i=j+1时k= 3i 当i=j时3i+1当i=j-1时 2i+j …………………………… 00… an-2,n-3 an-2,n-2 an-2,n-1 00… … an-1,n-2 an-1,n-1 三、对角矩阵 如果矩阵中的所有的非零元素都集中在以主对角线为中心的带状区域则称为对角矩阵。 常见的三对角矩阵,可按行的顺序压缩存储。
5.3.2 稀疏矩阵的压缩存储 矩阵中非零元素的个数远远小于矩阵元素的总数,这样的矩阵称为稀疏矩阵。 假设在m*n的矩阵中,有t个元素不为零,令δ=t/m*n,则称δ为矩阵的稀疏因子。通常认为δ≤0.05时,该矩阵为稀疏矩阵。
一、三元组表表示法 在存储稀疏矩阵时,为了节省存储单元,很自然地想到使用压缩存储方法。但由于非零元素的分布一般是没有规律的,因此在存储非零元素的同时,还必须同时记下它所在的行和列的位置(i,j)。 所以一个三元组(i,j,v)唯一确定了矩阵A的一个非零元。 其中v中存放非零元素的数值。因此,稀疏矩阵可由表示非零元的三元组及其行列数唯一确定。 以行优先的顺序将稀疏矩阵中的非零元素以三元组形式存放在一个数组中形成了三元组表。也可把三元组表看成一个线性表,线性表的每个结点对应稀疏矩阵的一个非零元素,这个线性表用顺序的方式存储在连续的存储区。
例如,将矩阵A转化为三元组表的形式。 0 8 0 0 4 0 0 0 0 0 0 0 0 0 0 0 0 5 0 0 0 0 0 7 0 0 0 0 2 0 0 0 0 6 0 0 0 9 0 0 0 0 A= 转化后的三元组表形式如下: ((0,1,8)(0,4,4),(2,3,5),(3,2,7),(4,0,2), (4,5,6),(5,2,9)) 加上(6,7)这一对行、列值便可作为矩阵A的另一种描述。
稀疏矩阵的三元组表存储表示的定义: #define Maxsize 255 typedef struct{ int i,j; datatype v; }Triple; typedef struct{ Triple data[Maxsize]; int mu,nu,tu; }TsMatrix;
算法4.6 首先我们来建立一个三元组表: void CreatSMatrix(int A1[ ][ ],TsMatrix *A) {int col,row,k; k=0; for (row=0;row<m;row++) for (col=0;col<n;col++) if (A1[row][col]!=0) {A->data[k].i=row; A->data[k].j=col; A->data[k].v=A1[row][col]; k++; } A->mu=m;A->nu=n;A->tu=k; } //m,n为要转换矩阵的行列数,定义为全局变量,在过程中直接使用
求转置矩阵: 转置是一种简单的矩阵运算。对于一个m×n的矩阵A,它的转置B是一个n×m的矩阵,且a[i][j]=b[j][i],0≦i≦m,0≦j≦n,即A的行是B的列,A的列是B的行。 for(col=0;col<n;col++) for(row=0;row<m;row++) b[col][row]=a[row][col]; //其时间复杂度为0(nu×mu) A= B=
如何将一个三元组进行转置? 将三元组A转置为B,就是将A的三元组表a.data置换为表B的三元组表b.data,如果只是简单地交换a.data中i和j的内容,那么得到的b.data将是一个按列优先顺序存储的稀疏矩阵B,要得到按行优先顺序存储的b.data,就必须重新排列三元组的顺序。 由于A的列是B的行,因此,按a.data的列序转置,所得到的转置矩阵B的三元组表b.data必定是按行优先存放的。 按这种方法设计的算法,其基本思想是:对A中的每一列 col(0≦col≦n-1),通过从头至尾扫描三元表a.data,找出所有列号等于col的那些三元组,将它们的行号和列号互换后依次放入b.data中,即可得到B的按行优先的压缩存储表示。
具体做法: 对矩阵M中的每一列col(0~nu-1),扫描所有的非零元素若有一个非零元素的列等于当前列col,则将该非零元素行列互换送到目标三元组表。
算法描述如下: void TransposeSMatrix(TsMatrix *a,TsMatrix *b) { int p,q,col; b->mu=a->nu;b->nu=a->mu;b->tu=a->tu; if (b->tu) { q=0; for (col=0;col<a->nu;++col ) for (p=0;p<a->tu;++p ) if (a->data[p].j==col) { b->data[q].i=a->data[p].j; b->data[q].j=a->data[p].i; b->data[q].v=a->data[p].v;++q; } } } //扫描A的所有列 //扫描所有非零元 演 示
分析该算法,其时间主要耗费在col和p的二重循环上,所以时间复杂度为O(nu*tu)。和通常存储方式下矩阵转置算法的时间复杂度O(mu*nu),该算法就显得不太理想了,虽然节省了存储空间却提高了时间复杂度。 怎样才能改进算法的效率呢?
如果能预先确定矩阵A中每一列(即B中每一行)的第一个非零元的位置,则在对三元组作转置时,就可以直接放到恰当的位置上。 如果能预先确定矩阵A中每一列(即B中每一行)的第一个非零元的位置,则在对三元组作转置时,就可以直接放到恰当的位置上。 为了确定这些位置,在转置前应首先求得A中每一列非零元的个数,进而求得每一列第一个元素在转置后应有的位置。 首先引入了两个辅助向量num和pos,其中num[col]表示矩阵A中第col列中非零元素的个数,pos[col]表示A中第col列的第一个非零元素在转置后的恰当位置。 则有: pos[0]=0 pos[col]=pos[col-1]+num[col-1] 1≤col≤A.nu-1
void FastTransposeSMatrix(TsMatrix *a,TsMatrix *b) { b->mu=a->nu; b->nu=a->mu; b->tu=a->tu; if (b->tu){ for (col=0;col<a->nu;col++ ) num[col]=0; for (t=0;t<a->tu;t++ ) ++num[a->data[t].j]; pos[0]=0; for (col=1;col<a->nu;col++ ) pos[col]= pos[col-1]+num[col-1]; for (p=0;p<a->tu;p++){ col=a->data[p].j;q= pos[col]; b->data[q].i=a->data[p].j; b->data[q].j=a->data[p].i; b->data[q].v=a->data[p].v;++ pos[col]; } } } //非零元的列号 O(nu+tu)
二、伪地址表示法 所谓伪地址就是元素在矩阵里按行优先(按列优先)顺序的相对位置(包括零元素一起算)。例如上述矩阵,按行优先顺序非零元的伪地址为: 用伪地址表示,线性表的每个结点包含两个域,一个是非零元素的伪地址,另一个是元素的值。这种方法共需2*tu(tu为非零元素个数)个存储单元,比三元组法节省,但要计算伪地址。m×n的稀疏矩阵A的元素A[i][j]的伪地址可用i*n+j 来计算。
三、十字链表 在十字链表中,稀疏矩阵的每一行用一个带头结点的循环链表表示,每一列也用一个带表头结点的循环链表表示。在这个结构中,除表头结点外,每一个节点都代表矩阵中的一个非零元素,它由五个域组成:行域,列域,值域,向下域和向右域。节点结构如下: 非零元素列号 非零元素行号 非零元素的值 向下域 向右域
col 2 3 1 val 8 4 5 row 2 2 4 down right 1 3 1 • 十字链表 • 设行指针数组和列指针数组,分别指向每行、列第一个非零元 • 结点定义 tpedef struct node { int row,col,val; struct node *down, *right; }JD; ^ ^ ^ ^ ^ ^ ^
5.4 广义表 LS=(a0,a1,…,an-1) 其中:LS是广义表的名称,n是它的长度。 在线性表中ai只限于单个元素。 在广义表中ai可以是单个元素也可以是广义表,分别称为广义表LS的原子和子表。 当n=0时称为广义表LS空表。 通常用大写字母A~Z表示广义表的名称,用小写字母a~z表示单个数据元素(原子),广义表用括号括起来,括号内的数据元素用逗号分隔开。 广义表有一定的层次,广义表的深度为它所含括号的重数,所以,空表的深度为1,原子的深度为0。 • 广义表的定义
当LS非空时,称第一个元素a1为LS的表头(Head),其余元素组成的表(a2,a3,…,an)称LS的表尾(Tail)。 当LS非空时,称第一个元素a1为LS的表头(Head),其余元素组成的表(a2,a3,…,an)称LS的表尾(Tail)。 • (1) A=( ) — A是一个空表,它的长度为0。 • (2) B=(e) —列表B只有一个原子e,长度为1。 • (3) C=(a,(b,c,d)) —列表C的长度为2,它的两个元素分 别为原子a和子表(b,c,d)。 • (4) D=(A,B,C) —列表D的长度为3,三个元素都是子表。 显然,若将它子表的值代入后,则有:D=((),(e),(a,(b,c,d)))。 它的表头是(),表尾是(B,C)。 • (5) E =(a,E),E是一个递归表,它的长度为2。 • 它相当于如下的无限列表:E=(a,(a,(a,…)))。 • 它的表头是原子a,表尾是(E)。
D A B C e a b c d 从定义和例子可以得到广义表的下列重要性质: (1)广义表是一种多层次的数据结构。广义表的元素可以是单元素,也可以是子表,而子表的元素还可以是子表。 (2)广义表可以是递归的表。广义表的定义并没有限制元素的递归,即广义表也可以是其自身的子表。例如表E就是一个递归的表。 (3)广义表可以为其他表所共享。例如,表A、表B、表C是表D的共享子表。在D中可以不必列出子表的值,而用子表的名称来引用。
定义可知,任何一个非空列表其表头可能是原子,也可能是列表,而其表尾必定为列表。例如:定义可知,任何一个非空列表其表头可能是原子,也可能是列表,而其表尾必定为列表。例如: • A=( ) B=(e) C=(a,(b,c,d)) D=(A,B,C) • Head(B)=e,Tail(B)=( ), • Head(C)= a Tail(C)=((b,c,d ) ) • Head(D)=A,Tail(D)=(B,C), • 由于(B,C)为非空表,可以继续分解: • Head((B,C))=B, Tail((B,C))=(C), • Head(E)=a,Tail(E)=(E) • 注意:广义表( )和(( ))不同,前者是空表,长度为n=0;后者长度n=1,它有一个元素是空表,可分解得到表头和表尾均是空表( )。
例:求下列广义表操作的结果(严题集5.10②)p33例:求下列广义表操作的结果(严题集5.10②)p33
结点结构 : 由于广义表中的数据元素有单元素和子表之分,相应地结点的结构形式也有两种: 一种是元素结点,用以表示单元素; 一种是子表结点,用以表示列表。 对于单元素结点,应包括元素值域和指向其后继结点的指针域; 对于子表结点,应该包括指向子表中第一个结点的表头指针域和指向后继结点的指针域。 为了区分这两类结点,在结点中设置一个标志域 如果标志为0,表示该结点为元素结点; 如果标志为1,则表示该结点为子表结点。 5.5 广义表的存储结构
子表结点 单元素结点 enum Boolean{0,1}; struct GLNode { Boolean tag; //tag=1表示有子表 union { ElemType data; //单元素的值 GLNode * sublist; //子表的头指针 }; GLNode * next; //后继结点 };
作业: 1. 已经顺序表A的元素有序递增,顺序表B的元素有序递减, 写一算法将B的所有元素插入到A中,并且插入后A仍保持原 序。(可设AB的数据元素类型为整型) 2. 设A=(a1,a2,…,an)和B=(b1,b2,…,bm)是两个带尾指针 的循环链表,写一算法将B表链接到A表的尾上,即使 A=( a1,a2,…,an , b1,b2,…,bm )。