5.1 多维数组. 5.1.1 多维数组的概念 数组是大家都已经很熟悉的一种数据类型,几乎所有高级语言程序设计中都设定了数组类型。在此,我们仅简单地讨论数组的逻辑结构及在计算机内的存储方式。. 1 .一维数组 一维数组可以看成是一个线性表或一个向量(第 2 章已经介绍),它在计算机内是存放在一块连续的存储单元中,适合于随机查找。这在第 2 章的线性表的顺序存储结构中已经介绍。. 2 .二维数组 二维数组可以看成是向量的推广。例如,设 A 是一个有 m 行 n 列的二维数组,则 A 可以表示为:. - PowerPoint PPT Presentation
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
5.1 多维数组5.1.1 多维数组的概念数组是大家都已经很熟悉的一种数据类型,几乎所有高级语言程序设计中都设定了数组类型。在此,我们仅简单地讨论数组的逻辑结构及在计算机内的存储方式。1.一维数组一维数组可以看成是一个线性表或一个向量(第 2 章已经介绍),它在计算机内是存放在一块连续的存储单元中,适合于随机查找。这在第 2 章的线性表的顺序存储结构中已经介绍。2.二维数组二维数组可以看成是向量的推广。例如,设 A 是一个有 m 行 n 列的二维数组,则 A 可以表示为:
在此,可以将二维数组 A 看成是由 m 个行向量 [X0 , X1 , …, Xm-1]T
组成,其中, Xi=( ai0, ai1, ….,ain-1) , 0≤i≤m-1 ;也可以将二维数组 A
5.1 多维数组5.1.1 多维数组的概念数组是大家都已经很熟悉的一种数据类型,几乎所有高级语言程序设计中都设定了数组类型。在此,我们仅简单地讨论数组的逻辑结构及在计算机内的存储方式。1.一维数组一维数组可以看成是一个线性表或一个向量(第 2 章已经介绍),它在计算机内是存放在一块连续的存储单元中,适合于随机查找。这在第 2 章的线性表的顺序存储结构中已经介绍。2.二维数组二维数组可以看成是向量的推广。例如,设 A 是一个有 m 行 n 列的二维数组,则 A 可以表示为:
在此,可以将二维数组 A 看成是由 m 个行向量 [X0 , X1 , …, Xm-1]T
组成,其中, Xi=( ai0, ai1, ….,ain-1) , 0≤i≤m-1 ;也可以将二维数组 A
3.十字链表当稀疏矩阵中非零元的位置或个数经常变动时,三元组就不适合于作稀疏矩阵的存储结构,此时,采用链表作为存储结构更为恰当。十字链表为稀疏矩阵中的链接存储中的一种较好的存储方法,在该方法中,每一个非零元用一个结点表示,结点中除了表示非零元所在的行、列和值的三元组( i,j,v )外,还需增加两个链域:行指针域( rptr ),用来指向本行中下一个非零元素;列指针域( cptr ),用来指向本列中下一个非零元素。稀疏矩阵中同一行的非零元通过向右的 rptr指针链接成一个带表头结点的循环链表。同一列的非零元也通过 cptr指针链接成一个带表头结点的循环链表。因此,每个非零元既是第 i 行循环链表中的一个结点,又是第 j 列循环链表中的一个结点,相当于处在一个十字交叉路口,故称链表为十字链表。另外,为了运算方便,我们规定行、列循环链表的表头结点和表示非零元的结点一样,也定为五个域,且规定行、列、域值为 0( 因此,为了使表头结点和表示非零元的表结点不发生混淆,三元组中,输入行和列的下标不能从0开始 !!! 而必须从 1开始 ) ,并且将所有的行、列链表和头结点一起链成一个循环链表。在行(列)表头结点中,行、列域的值都为 0 ,故两组表头结点可以共用,即第 i 行链表和第 i 列链表共用一个表头结点,这些表头结点本身又可以通过 V域(非零元值域,但在表头结点中为 next ,指向下一个表头结点)相链接。另外,再增加一个附加结点(由指针 hm指示,行、列域分别为稀疏矩阵的行、列数目),附加结点指向第一个表头结点,则整个十字链表可由hm指针惟一确定。
例如,图5-6的稀疏矩阵 M 的十字链表描述形式见图 5-10 。十字链表的数据类型描述如下:struct linknode{ int i, j;struct linknode *cptr, *rptr;union vnext /* 定义一个共用体 */{ int v; /* 表结点使用 V域 , 表示非零元值 */struct linknode next; /* 表头结点使用 next域*/ } k; }
图 5-10 稀疏矩阵的十字链表
5.4.2 稀疏矩阵的运算
1.稀疏矩阵的转置运算下面将讨论三元组表上如何实现稀疏矩阵的转置运算。转置是矩阵中最简单的一种运算。对于一个 mn 的矩阵 A ,它的转置矩阵 B 是一个 nm 的,且 B[i][j]=A[j][i] , 0≤i<n,0≤j<m 。例如,图 5-6给出的 M 矩阵和图 5-7给出的 N 矩阵互为转置矩阵。在三元组表表示的稀疏矩阵中,怎样求得它的转置呢 ? 从转置的性质知道,将 A转置为 B ,就是将 A 的三元组表 a.data 变为 B 的三元组表 b.data ,这时可以将 a.data 中 i 和 j 的值互换,则得到的 b.data 是一个按列优先顺序排列的三元组表,再将它的顺序适当调整,变成行优先排列,即得到转置矩阵 B 。下面将用两种方法处理:( 1 )按照 A 的列序进行转置由于 A 的列即为 B 的行,在 a.data 中,按列扫描,则得到的 b.data 必按行优先存放。但为了找到 A 的每一列中所有的非零的元素,每次都必须从头到尾扫描 A 的三元组表(有多少列,则扫描多少遍),这时算法描述如下:
分析这个算法,主要工作在 col 和 ano 二重循环上,故算法的时间复杂度为 O(a.cols*a.terms) 。而通常的 m×n 阶矩阵转置算法可描述为:for(col=0; col<n; col++)for (row=0;row<m;row++)b[col][row]=a[row][col];它的时间复杂度为 O(m×n) 。而一般的稀疏矩阵中非零元个数 a.terms远大于行数 m ,故压缩存储时,进行转置运算,虽然节省了存储单元,但增大了时间复杂度,故此算法仅适应于 a.terns<<a.rows a.cols 的情形。
( 2 )按照 A 的行序进行转置即按 a.data 中三元组的次序进行转置,并将转置后的三元组放入 b 中恰当的
位置。若能在转置前求出矩阵 A 的每一列 col (即 B 中每一行)的第一个非零元转置后在 b.data 中的正确位置 pot[col] ( 0≤col<a.cols ),那么在对 a.data 的三元组依次作转置时,只要将三元组按列号 col 放置到 b.data[pot[col]] 中,之后将 pot[col] 内容加 1 ,以指示第 col 列的下一个非零元的正确位置。为了求得位置向量pot ,只要先求出 A 的每一列中非零元个数 num[col] ,然后利用下面公式:
该算法比按列转置多用了辅助向量空间 pot ,但它的时间为四个单循环,故总的时间复杂度为 O(a.cols+a.terms) ,比按列转置算法效率要高。
2.稀疏矩阵的相加运算当稀疏矩阵用三元组表进行相加时,有可能出现非零元素的位置变动,这时候,不宜采用三元组表作存储结构,而应该采用十字链表较方便。( 1 )十字链表的建立下面分两步讨论十字链表的建立算法:第一步,建立表头的循环链表:依次输入矩阵的行、列数和非零元素个数: m,n 和 t 。由于行、列链表共享一组表头结点,因此,表头结点的个数应该是矩阵中行、列数中较大的一个。假设用 s 表示个数,即 s=max ( m , n )。依次建立总表头结点(由 hm指针指向)和 s 个行、列表头结点,并使用 next域使 s+1 个头结点组成一个循环链表,总表头结点的行、列域分别为稀疏矩阵的行、列数目, s 个表头结点的行列域分别为 0 。并且开始时,每一个行、列链表均是一个空的循环链表,即 s 个行、列表头结点中的行、列指针域 rptr 和 cptr均指向头结点本身。第二步,生成表中结点:依次输入 t 个非零元素的三元组( i,j,v ),生成一个结点,并将它插入到第 i 行链表和第 j 列链表中的正确位置上,使第 i 个行链表和第 j 个列链表变成一个非空的循环链表。在十字链表的建立算法中,建表头结点,时间复杂度为 O(s) ,插入 t 个非零元结点到相应的行、列链表的时间复杂度为 O ( t*s ),故算法的总的时间复杂度为 O(t*s) 。
( 2 )用十字链表实现稀疏矩阵相加运算假设原来有两个稀疏矩阵 A 和 B ,如何实现运算 A=A+B 呢?假设原来 A 和 B 都用十字链表作存储结构,现要求将 B 中结点合并到 A 中,合并后的结果有三种可能: 1 )结果为 aij+bij ; 2 ) aij ( bij=0 );3 ) bij ( aij=0 )。由此可知当将 B 加到 A 中去时,对 A 矩阵的十字链表来说,或者是改变结点的 v域值( aij+bij≠0 ),或者不变( bij=0
),或者插入一个新结点( aij=0 ),还可能是删除一个结点( aij+bij=
0 )。于是整个运算过程可以从矩阵的第一行起逐行进行。对每一行都从行表头出发分别找到 A 和 B 在该行中的第一个非零元结点后开始比较,然后按上述四种不同情况分别处理之。若 pa 和 pb 分别指向 A 和 B 的十字链表中行值相同的两个结点,则 4 种情况描述为:1 ) pa->j=pb->j 且 pa->k.v+pb->k.v≠0 ,则只要将 aij+bij 的值送到 pa所指结点的值域中即可,其他所有域的值都不变化。2 ) pa->j=pb->j且 pa->k.v+pb->k.v=0, 则需要在 A 矩阵的链表中删除pa 所指的结点。这时,需改变同一行中前一结点的 rptr域值,以及同一列中前一结点的 cptr域值。3 ) pa->j<pb->j且 pa->j≠0 ,则只要将 pa指针往右推进一步,并重新加以比较即可。4 ) pa->j>pb->j 或 pa->j=0 ,则需在 A 矩阵的链表中插入 pb 所指结点。
下面将对矩阵 B 加到矩阵 A 上面的操作过程大致描述如下 :设 ha 和 hb 分别为表示矩阵 A 和 B 的十字链表的总表头; ca 和 cb 分别为指向 A 和 B 的行链表的表头结点,其初始状态为: ca=ha->k.next ; cb=hb->k.next;pa 和 pb 分别为指向 A 和 B 的链表中结点的指针。开始时, pa=ca->rptr; pb=cb->rptr; 然后按下列步骤执行:①当 ca->i=0 时,重复执行②、③、④步,否则,算法结束;②当 pb->j≠0 时,重复执行③步,否则转第④步;③比较两个结点的列序号,分三种情形:a.若 pa->j<pb->j 且 pa->j≠0 ,则令 pa指向本行下一结点,即 qa=pa; pa=pa->rptr; 转②步 ;b.若 pa->j>pb->j 或 pa->j=0 ,则需在 A 中插入一个结点。假设新结点的地址为 P ,则 A 的行表中指针变化为: qa->rptr=p;p->rptr=pa;同样, A 的列表中指针也应作相应改变,用 hl[j]指向本列中上一个结点,则 A 的列表中指针变化为: p->cptr=hl[j]->cptr; hl[j]->cptr=p; 转第②步;c.若 pa->j=pb->j ,则将 B 的值加上去,即 pa->k.v=pa->k.v+bp->k.v ,此时若 pa->k.v≠0 ,则指针不变,否则,删除 A 中该结点,于是行表中指针变为: qa->rptr=pa->rptr; 同时,为了改变列表中的指针,需要先找同列中上一个结点,用 hl[j] 表示,然后令 hl[j]->cptr=pa->cptr ,转第②步。④一行中元素处理完毕后,按着处理下一行,指针变化为: ca=ca->k.next; cb=cb->k.next;转第 1)步。
1.广义表的定义广义表是 n≥0 个元素 a1 , a2 ,…, an 的有限序列,其中每一个 ai 或者是原子,或者是一个子表。广义表通常记为 LS=(a1,a2,…,an) ,其中 LS 为广义表的名字,n 为广义表的长度, 每一个 ai 为广义表的元素。但在习惯中,一般用大写字母表示广义表,小写字母表示原子。
3.广义表的表示方法( 1 )用 LS=(a1 , a2 ,…, an) 形式,其中每一个 ai 为原子或广义表例如: A=(b,c) B=(a,A) E=(a,E)都是广义表。( 2 )将广义表中所有子表写到原子形式,并利用圆括号嵌套例如,上面提到的广义表 A 、 B 、 C 可以描述为:A(b,c)B(a,A(b,c))E(a,E(a,E (…) ))( 3 )将广义表用树和图来描述上面提到的广义表 A 、 B 、 C 的描述见图 5-11 。