640 likes | 818 Views
ç¬¬äº”ç« æ•°ç»„å’Œå¹¿ä¹‰è¡¨. å¦ä¹ è¦ç‚¹ 掌æ¡æ•°ç»„在以行åºä¸ºä¸»çš„å˜å‚¨ä¸çš„地å€è®¡ç®—方法。 掌æ¡ç‰¹æ®ŠçŸ©é˜µå’Œç¨€ç–矩阵的å˜å‚¨æ–¹æ³•ã€‚ 掌æ¡å¹¿ä¹‰è¡¨çš„结构特点åŠå…¶å˜å‚¨è¡¨ç¤ºæ–¹æ³•ã€‚. 5.1 数组的类型定义. 5.2 数组的顺åºè¡¨ç¤ºå’Œå®žçŽ°. 5.3 矩阵的压缩å˜å‚¨. 5.4 广义表的类型定义. 5.5 广义表的表示方法. 5.1 数组的类型定义. 数组的定义:
E N D
第五章 数组和广义表 • 学习要点 • 掌握数组在以行序为主的存储中的地址计算方法。 • 掌握特殊矩阵和稀疏矩阵的存储方法。 • 掌握广义表的结构特点及其存储表示方法。
5.1 数组的类型定义 5.2 数组的顺序表示和实现 5.3 矩阵的压缩存储 5.4 广义表的类型定义 5.5广义表的表示方法
5.1 数组的类型定义 数组的定义: 数组是由下标和值组成的序对的有限集合。数组中每组有定义的下标,都存在一个与其对应的值,这个值称为数组元素。即数组中的每个数据元素都对应一组下标( j1,j2,…,jn),每个下标的取值范围是0≤ji≤bi-1,bi称为第i维的长度( i=1,2,…,n)。 当n=1时,n维数组就蜕化为定长的线性表。
ADT Array { 数据对象: D={aj1,j2, ...,,ji,jn| ji =0,...,bi -1, i=1,2,..,n } 数据关系: R={R1, R2, ..., Rn} Ri={<aj1,... ji,... jn, aj1, ...ji +1, ...jn> | 0 jk bk -1, 1 k n 且k i, 0 ji bi -2, i=2,...,n } } ADT Array 基本操作:
二维数组的定义: 数据对象: D = {aij | 0≤i≤b1-1, 0 ≤j≤b2-1} 数据关系: R = { ROW, COL } ROW = {<ai,j,ai+1,j>| 0≤i≤b1-2, 0≤j≤b2-1} COL = {<ai,j,ai,j+1>| 0≤i≤b1-1, 0≤ j≤b2-2}
( ) ( ) ( ) ( ) ( ) ( ) ( ) ( ) 例如,对于如下形式的m*n阶矩阵,可以用二维数组表示: Am×n= A=(a0,a1,…,ap) (p=n-1或m-1)
5.2 数组的顺序表示和实现 类型特点: 1) 只有引用型操作,没有加工型操作; 2) 数组是多维的结构,而存储空间是 一个一维的结构。 • 有两种顺序映象的方式: • 1)以行序为主序; • 2)以列序为主序。
0 按列序为主序存放 1 m-1 m 2m-1 Loc( aij )= Loc( a00 ) + ( j×m + i )×L ? m*n-1
0 按行序为主序存放 1 n-1 n 2n-1 Loc( aij )= Loc( a00 ) + ( i×n + j )× L ? m*n-1
以“行序为主序”的存储映象 例如: a0,0 a0,1 a0,2 a1,0 a1,1 a1,2 a0,0 a0,1 a0,2 a1,0 a1,1 a1,2 L 二维数组A中任一元素ai,j的存储位置 LOC(i,j) = LOC(0,0) + (b2×i+j)× L 称为基地址或基址。
推广到一般情况,可得到 n 维数组数据元素存储位置的映象关系 n LOC(j1, j2, ..., jn ) = LOC(0,0,...,0) + ∑ ci ji =1 i 其中 cn = L,ci-1 = bi ×ci , 1 < i n。 称为 n 维数组的映象函数。数组元素 的存储位置是其下标的线性函数。
例:假设有二维数组A[M,N],设A[0,0]在644处,A[2,2] 在676处。每个元素占一个存储空间,则A[4,5]的地址为多少?以行序为主序存储。
5.3 矩阵的压缩存储 在某些矩阵中,往往会出现许多值相同的元素或零元素,为了节省存储空间,可对这类矩阵进行压缩存储。压缩存储的原则是:对多个值相同的元素只分配一个存储空间,对零元不分配空间。 我们把相同的元素或零元素在矩阵中的有一定的规律分布称为特殊矩阵,如果矩阵中零元素占绝大部分的称为稀疏矩阵。
两类稀疏矩阵: 1) 特殊矩阵 非零元在矩阵中的分布有一定规则 例如: 三角矩阵 对角矩阵 2) 随机稀疏矩阵 非零元在矩阵中随机出现
5.3.1 特殊矩阵 一、对称矩阵 在一个n阶方阵A中,若元素满足下述性质: aij=aji 1≤i,j≤n, 则称A为对称矩阵。 不失一般性,我们按“行优先顺序”存储主对角线(包括对角线)以下的元素。在这个下三角矩阵中,第i行恰有i个元素,元素总数为:n(n+1)/2.
a11 a12…a1n a21a22… a2n … … … … an1an2… ann
a00 a01…a0n-1 a10a11… a1n-1 … … … … an-1,0an-1,1… an-1,n-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
三、对角矩阵 如果矩阵中的所有的非零元素都集中在以主对角线为中心的带状区域则称为对角矩阵。 常见的三对角矩阵,可按行的顺序压缩存储。
a00 a01 0…………… . 0 a10 a11a120…………… 0 0a21 a22 a230 ……… 0 …………………………… 00… an-2,n-3 an-2,n-2an-2,n-1 00… … an-1,n-2 an-1,n-1
5.3.2 稀疏矩阵 何谓稀疏矩阵? 假设 m 行 n 列的矩阵含 t 个非零元素,则称 为稀疏因子。 通常认为 0.05 的矩阵为稀疏矩阵。
随机稀疏矩阵的压缩存储方法: 一、三元组顺序表 二、 十字链表
一、三元组表表示法 在存储稀疏矩阵时,为了节省存储单元,很自然地想到使用压缩存储方法。但由于非零元素的分布一般是没有规律的,因此在存储非零元素的同时,还必须同时记下它所在的行和列的位置(i,j)。 反之一个三元组(i,j,v)唯一确定了矩阵A的一个非零元。其中v中存放非零元素的数值。因此,稀疏矩阵可由表示非零元的三元组及其行列数唯一确定。
以行优先的顺序将稀疏矩阵中的非零元素以三元组形式存放在一个数组中形成了三元组表。也可把三元组表看成一个线性表,线性表的每个结点对应稀疏矩阵的一个非零元素,这个线性表用顺序的方式存储在连续的存储区。 以行优先的顺序将稀疏矩阵中的非零元素以三元组形式存放在一个数组中形成了三元组表。也可把三元组表看成一个线性表,线性表的每个结点对应稀疏矩阵的一个非零元素,这个线性表用顺序的方式存储在连续的存储区。
例:将矩阵M转化为三元组表的形式。 0 129 0 0 0 0 0 0 0 0 0 0 0 -3 0 0 0 0 14 0 0 0 24 0 0 0 0 0 18 0 0 0 0 0 15 0 0 -7 0 0 0 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)这一对行、列值便可作为矩阵M的另一种描述。
0 129 0 0 0 0 0 0 0 0 0 0 0 -3 0 0 0 0 14 0 0 0 24 0 0 0 0 0 18 0 0 0 0 0 15 0 0 -7 0 0 0 M =
稀疏矩阵的三元组表存储表示的定义: #define MAXSIZE 12500 typedef struct { int i, j; //该非零元的行下标和列下标 ElemType e; // 该非零元的值 } Triple; // 三元组类型 typedef union { Triple data[MAXSIZE + 1]; int mu, nu, tu; } TSMatrix; // 稀疏矩阵类型
建立一个三元组表: void CreatSMatrix(TSMatrix &M ,int A[m][n]){ //m,n为要转换矩阵的行列数,定义为常量 k=1; for (row=0;row<m;row++) for (col=0;col<n;col++) if (A[row][col]!=0){ M.data[k].i=row+1; M.data[k].j=col+1; M.data[k].v=A [row][col]; k++; } M.mu=m;M.nu=n;M.tu=k; }
如何求转置矩阵? 0 129 0 0 0 0 0 0 0 0 0 0 0 -3 0 0 0 0 14 0 0 0 24 0 0 0 0 0 18 0 0 0 0 0 15 0 0 -7 0 0 0 0 0 -3 0 0 15 12 0 0 0 18 0 9 0 0 24 0 0 0 0 0 0 0 0 0 0 0 0 0 -7 0 0 14 0 0 0 0 0 0 0 0 0
求转置矩阵: 转置是一种简单的矩阵运算。对于一个m×n的矩阵M,它的转置T是一个n×m的矩阵,且M[i][j]=T[j][i],即M的行是T的列,M的列是T的行。 for(col=0;col<n;col++) for(row=0;row<m;row++) T[col][row]=M[row][col]; //其时间复杂度为O(n×m)
如何将一个三元组进行转置? 将三元组M转置为T,就是将M的三元组表a.data置换为表T的三元组表b.data,如果只是简单地交换a.data中i和j的内容,那么得到的b.data将是一个按列优先顺序存储的稀疏矩阵T,要得到按行优先顺序存储的b.data,就必须重新排列三元组的顺序。 由于M的列是T的行,因此,按a.data的列序转置,所得到的转置矩阵T的三元组表b.data必定是按行优先存放的。
按这种方法设计的算法,其基本思想是:对M中的每一列 col(0≦col≦n-1),通过从头至尾扫描三元表a.data,找出所有列号等于col的那些三元组,将它们的行号和列号互换后依次放入b.data中,即可得到T的按行优先的压缩存储表示。
(1, 3, -3) ③ (2, 1, 12) ① 1 1 2 2 ⑤ (1, 6, 15) 3 3 ⑧ 4 4 (3, 1, 9) 5 5 (2, 5, 18) 6 6 (4, 6, -7) 7 7 ⑥ (5, 3, 14) ⑦ 8 8 (3, 4, 24) ④ ② • 矩阵转置方法一 • 由于M的列是T的行,按M.data的列序转置,所得到的转置矩阵T的三元组表T.data必定是按行优先存放。 思路:反复扫描M.data中的列序,从小到大依次进行转置。 (1, 2, 12) (1, 3, 9 ) 三 元 组 表 a.data 三 元 组 表 b.data (3, 1, -3) (3, 5, 14) (4, 3, 24) (5, 2, 18) (6, 1, 15) (6, 4, -7)
Status TranscpoteSMatrix(TsMatrix M,TsMatrix &T){ T.mu=M.nu;T.nu=M.mu;T.tu=M.tu; if (T.tu){ q=1; for (col=1;col<=M.nu;++col ) for (p=1;p<=M.tu;++p ) if (M.data[p].j==col){ T.data[q].i=M.data[p].j; T.data[q].j=M.data[p].i; T.data[q].v=M.data[p].v;++q;}//if } //if return OK; }
4 1 2 3 1 3 6 2 1 3 3 6 5 1 6 4 12 15 9 24 -7 14 18 -3 7 6 8 i i j j v v 7 8 0 1 2 3 4 5 6 6 7 8 1 0 5 4 3 2 1 1 3 3 6 5 6 4 6 1 3 1 2 2 3 6 7 4 -7 8 24 15 14 18 12 9 -3 q q q q q p p p p p p p p p p p p p p p p M.data T.data • 矩阵的转置方法一的演示 col=1 col=2
如果能预先确定矩阵A中每一列(即B中每一行)的第一个非零元的位置,则在对三元组作转置时,就可以直接放到恰当的位置上。 如果能预先确定矩阵A中每一列(即B中每一行)的第一个非零元的位置,则在对三元组作转置时,就可以直接放到恰当的位置上。 为了确定这些位置,在转置前应首先求得A中每一列非零元的个数,进而求得每一列第一个元素在转置后应有的位置。
④ (1, 3, -3) ③ (1, 6, 15) (5, 3, 14) (2, 5, 18) ① (4, 6, -7) ② ⑤ ⑥ ⑧ (2 ,1, 12) ⑦ (3, 4, 24) 1 1 2 2 (3, 1, 9) 3 3 4 4 5 5 6 6 7 7 8 8 • 矩阵转置方法二 • 依次把M.data中的元素直接送入T.data的恰当位置上(即M三元组的p指针不回溯)。 (1, 2, 12) (1, 3, 9 ) (3, 1, -3) (3, 5, 14) (4, 3, 24) (5, 2, 18) (6, 1, 15) (6, 4, -7)
首先引入两个辅助向量num和cpot,其中num[col]表示矩阵A中第col列中非零元素的个数,cpot[col]表示A中第col列的第一个非零元素在转置后的恰当位置。 首先引入两个辅助向量num和cpot,其中num[col]表示矩阵A中第col列中非零元素的个数,cpot[col]表示A中第col列的第一个非零元素在转置后的恰当位置。 则有: cpot[1]=1 cpot[col]=cpot[col-1]+num[col-1]
0 12 9 0 0 0 0 0 0 0 0 0 0 0 -3 0 0 0 0 14 0 0 0 24 0 0 0 0 0 18 0 0 0 0 0 15 0 0 -7 0 0 0 M = col 3 6 7 1 2 4 5 2 num[col] 1 0 2 2 1 0 8 9 cpot[col] 1 3 7 8 5
转置后每一行的第一个非零元在三元组中的位置的确定。 转置后每一行的第一个非零元在三元组中的位置的确定。 for (col=1; col<=M.nu; ++col) num[col] = 0; for (t=1; t<=M.tu; ++t) ++num[M.data[t].j]; cpot[1] = 1; for (col=2; col<=M.nu; ++col) cpot[col] = cpot[col-1] + num[col-1];
Status FastTranscpoteSMatrix(TSMatrix M, TSMatrix &T){ T.mu = M.nu; T.nu = M.mu; T.tu = M.tu; if (T.tu){ 确定每一行首元素位置 for (p=1; p<=M.tu; ++p) { } } // if return OK; } // FastTranscpoteSMatrix 填入转置矩阵元素
col = M.data[p].j; q = cpot[col]; T.data[q].i = M.data[p].j; T.data[q].j = M.data[p].i; T.data[q].e = M.data[p].e; ++cpot[col]
分析算法FastTranscpoteSMatrix的时间复杂度: for (col=1; col<=M.nu; ++col) … … for (t=1; t<=M.tu; ++t) … … for (col=2; col<M.nu; ++col) … … for (p=1; p<=M.tu; ++p) … … 时间复杂度为: O(M.nu+M.tu)
6 1 4 7 2 3 2 1 3 1 3 6 6 4 5 1 3 6 -3 8 14 12 18 -7 9 24 15 col 7 i i j j v v num[col] 0 6 1 7 2 8 0 0 1 3 4 5 5 6 3 7 2 8 4 4 1 3 5 1 6 6 6 3 3 6 7 1 2 4 3 1 2 -3 24 14 -7 18 12 8 15 9 9 cpot[col] p p p p p p p p 3 4 1 2 5 6 2 0 2 2 1 1 7 1 8 5 8 3 • 实现过程演示 2 4 6 9 3 5 7
二、 十字链表 用一个节点代表矩阵中的一个非零元素,它由五个域组成:行域,列域,值域,向下域和向右域。节点结构如下: 非零元素列号 非零元素行号 非零元素的值 向下域 向右域
稀疏矩阵的十字链表存储结构表示如下: typedef struct OLNode{ int i,j; ElemType e; struct OLNode *right,*down; }*Olink; typedef struct { OLNode *rhead,*chead; int mu,nu,tu; }CrossList;
M.chead M.rhead 1 1 3 1 4 5 ^ ^ 2 2 -1 ^ ^ 3 1 2 ^ ^ ^ 3 0 0 5 0 -1 0 0 2 0 0 0
5.4 广义表的定义 ADT Glist { 数据对象:D={ei | i=1,2,..,n; n≥0; ei∈AtomSet 或 ei∈GList, AtomSet为某个数据对象 } 数据关系: LR={<ei-1, ei >| ei-1 ,ei∈D, 2≤i≤n} 基本操作: } ADT Glist