340 likes | 490 Views
数据结构 第 5 章 数组和广义表. 什么是数组和广义表?? 数组和广义表的存储结构. 数组和广义表可看成是一种特殊的线性表,其特殊在于,表中的数据元素本身也是一种线性表。. 数组. 数组:是所有高级编程语言中都已实现的固有数据类型。但它和其它诸如整数、实数等原子类型不同,它是一种结构类型。换句话说, " 数组 " 是一种数据结构。 一维数组 A[n1] :定长的线性表 二维数组 A[n1][n2] :每个数据元素也是一个定长的线性表的线性表。
E N D
数据结构第5章 数组和广义表 • 什么是数组和广义表?? • 数组和广义表的存储结构 数据结构
数组和广义表可看成是一种特殊的线性表,其特殊在于,表中的数据元素本身也是一种线性表。数组和广义表可看成是一种特殊的线性表,其特殊在于,表中的数据元素本身也是一种线性表。 数据结构
数组 • 数组:是所有高级编程语言中都已实现的固有数据类型。但它和其它诸如整数、实数等原子类型不同,它是一种结构类型。换句话说,"数组"是一种数据结构。 • 一维数组A[n1]:定长的线性表 • 二维数组A[n1][n2]:每个数据元素也是一个定长的线性表的线性表。 可以将二维数组A看成是由m个行向量[X0,X1, …,Xm-1]T组成,其中,Xi=( ai0, ai1, ….,ain-1), 0≤i≤m-1;也可以将二维数组A看成是由n个列向量[y0, y1, ……,yn-1]组成,其中 yi=(a0i, a1i, …..,am-1i),0≤i≤n-1。 数据结构
数组的抽象数据类型定义 ADT Array{ 数据对象: D={aj1j2..jn|n>=0称为数组的维数, ji是数组元素的第i维下标 值,aj1j2...jn (-ElemSet} 数据关系: 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, aj1...ji...jn,aj1...ji+1 ...jn(-D,i=2,...n} 基本操作: InitArray(&A,n,bound1,...,boundn) 构造相应的数组A,并返回OK. DestroyArray(&A) 销毁数组A. Value(A,&e,index1,...,indexn) e赋值为所指定的A的元素值. Assign(&A,e,index1,...,indexn) 将e的值赋给所指定的A的元素. }ADT Array 数组一旦被定义,其维数(N)和每一维的上、下界均不能再变,数组中元素之间的关系也不再改变。因此数组的基本操作除初始化和结构销毁之外,只有通过给定的"一组下标"索引取得元素或修改元素的值。 数据结构
数组的顺序表示和实现 • 从理论上讲,数组结构也可以使用两种存储结构,即顺序存储结构和链式存储结构。然而,由于数组结构没有插入、删除元素的操作,也就是说,数组一旦建立,结构中的元素个数和元素间的关系就不再发生变化。因此,一般都是采用顺序存储的方法来表示数组。 • 组成数组结构的元素可以是多维的,但存储数据元素的内存单元地址是一维的,因此,在存储数组结构之前,需要将多维关系映射到一维关系。 • 通常有两种映射方法:即"以行(序)为主(序)"的映象方法和"以列(序)为主(序)"的映象方法。 数据结构
⑴以行(序)为主(序)——将数组元素按行排列,第i+1个行向量紧接在第i个行向量后面。⑴以行(序)为主(序)——将数组元素按行排列,第i+1个行向量紧接在第i个行向量后面。 • 以二维数组为例, A的m*n个元素以行(序)为主(序)存储的线性序列为: a00,a01,…,a0n-1,a10,a11,…a1n-1,…,am-10,am-11,…,am-1n-1 • 推广至n维的规律为:最左边下标变化最慢,最右边下标变化最快,右边下标变化一遍,与之相邻的左边下标才变化一次。因此,在算法中,最左边下标可以看成是外循环,最右边下标可以看成是最内循环。 • 在PASCAL、C语言中,数组就是以行(序)为主(序)存储的。 ⑵以列(序)为主(序)——将数组元素按列向量排列,第j+1个列向量紧接在第j个列向量之后。 • 以二维数组为例, A的m*n个元素以列(序)为主(序)存储的线性序列为: a00,a10,…,am-10,a01,a11,…am-11,…,a0n-1,a1n-1,…,am-1n-1 • 推广至n维的规律为:最右边下标变化最慢,最左边下标变化最快,左边下标变化一遍,与之相邻的右边下标才变化一次。因此,在算法中,最右边下标可以看成是外循环,最左边下标可以看成是最内循环。 • 在FORT0RAN语言中,数组就是按列优先顺序存储的。 数据结构
可见,对于数组,一旦规定了它的维度和各维的长度,便可为它根据上述方式分配存储空间。可见,对于数组,一旦规定了它的维度和各维的长度,便可为它根据上述方式分配存储空间。 • 反之,只要给出数组中某元素的下标值,就可得出其存储位置。 • 假设每个数据元素占L个存储单元,则二维数组Amn中任一元素aij的存储位置 • 在以以行(序)为主(序)存储时,可由下式确定: LOC(aij)=LOC(a00)+(i×n+j)×L • 在以以列(序)为主(序)存储时,可由下式确定: LOC(aij)=LOC(a00)+(j×m+i)×L • 推广开来,在以以行(序)为主(序)存储时, • 三维数组Ab1b2b3中任一元素aj1j2j3的存储位置,可由下式确定: LOC(aj1j2j3)=LOC(a000)+(j1×b2×b3+j2×b3+j3)×L • N维数组Ab1b2…bn中任一元素aj1j2…jn的存储位置,可由下式确定: LOC(aij)= LOC(a00…0)+(j1×b2×…×bn+j2×b3×…×bn+…+jn-1× bn +jn)×L 数据结构
矩阵的压缩存储 • 矩阵是一个二维数组,它是很多科学与工程计算问题中研究的数学对象。 • 矩阵可以用行优先或列优先方法顺序存放到内存中,但是,当矩阵的阶数很大时将会占较多存储单元。而当矩阵中的非零元素分布呈现某种规律时,从节约存储单元的角度出发,可考虑进行压缩存储。 • 所谓压缩存储是指:为多个值相同的元素只分配一个存储空间,而对值为零的元素不分配空间。 数据结构
特殊矩阵及其存储 • 特殊矩阵就是元素值的排列具有一定规律的矩阵。常见的特殊矩阵有:对称矩阵、下(上)三角矩阵、对角线矩阵等等。 • 对称矩阵:在一个n阶方阵A中,若元素满足 aij=aji 0≦i,j≦n-1 则称A为对称矩阵 对称矩阵中的元素关于主对角线对称,故只要存储矩阵中上三角或下三角中的元素,让每两个对称的元素共享一个存储空间,这样,能节约近一半的存储空间。不失一般性,我们按“行优先顺序”存储主对角线(包括对角线)以下的元素,其存储形式如图所示: 数据结构
在这个下三角矩阵中,第i行恰有i+1个元素,元素总数为n(n+1)/2.因此,我们可以按图中自上到下,自左到右的次序将这些元素存放在一个向量sa[0..n(n+1)/2-1]中。在这个下三角矩阵中,第i行恰有i+1个元素,元素总数为n(n+1)/2.因此,我们可以按图中自上到下,自左到右的次序将这些元素存放在一个向量sa[0..n(n+1)/2-1]中。 • 为了便于访问对称矩阵A中的元素,我们必须在aij和sa[k]之间找一个对应关系。 • 若i≧j,则aij在下三角形中。aij之前的i行(从第0行到第i-1行)一共有1+2+…+i=i(i+1)/2个元素,在第i行上,aij之前恰有j个元素,因此有: k=i*(i+1)/2+j • 若i<j,则aij是在上三角矩阵中。因为aij=aji,所以只要交换上述对应关系式中的i和j即可得到: k=j*(j+1)/2+i • sa[0..n(n+1)/2-1]称为n阶对称矩阵A的压缩存储。 数据结构
三角矩阵:下(上)三角矩阵的特点是以主对角线为界的上(下)半部分是一个固定的值c,下(上)半部分的元素值没有任何规律。三角矩阵:下(上)三角矩阵的特点是以主对角线为界的上(下)半部分是一个固定的值c,下(上)半部分的元素值没有任何规律。 • 三角矩阵的压缩存储与上述的对称矩阵的压缩存储一样,只存储其下(上)三角中的元素。除此之外,需再增加一个存储常数c的存储空间即可。 数据结构
对角矩阵:对角矩阵的特点是所有的非零元素都集中在以主对角线为中心的带状区域中。即除了主对角线和主对角线相邻两侧的若干条对角线上的元素之外,其余元素皆为零。对角矩阵:对角矩阵的特点是所有的非零元素都集中在以主对角线为中心的带状区域中。即除了主对角线和主对角线相邻两侧的若干条对角线上的元素之外,其余元素皆为零。 • 对角矩阵可按行优先顺序或对角线的顺序,将其压缩存储到一个向量中,并且也能找到每个非零元素和向量下标的对应关系。 • 以三对角矩阵为例,三对角矩阵除第0行和第n-1行是2个元素外,每行的非零元素都要是3个,因此,可以用一个包含3n-2个元素的数组sa存储三对角矩阵。 • 数组sa中的元素sa[k]与三对角带状矩阵中的元素aij存在一一对应关系,在aij之前有i行,共有3*i-1个非零元素,在第i行,有j-i+1个非零元素,这样,非零元素aij的地址为: LOC(i,j)=LOC(0,0)+[3*i-1+(j-i+1)]*L=LOC(0,0)+(2i+j)*L 数据结构
稀疏矩阵及其存储 • 上述的各种特殊矩阵,其非零元素的分布都是有规律的,因此总能找到一种方法将它们压缩存储到一个向量中,并且一般都能找到矩阵中的元素与该向量的对应关系,通过这个关系,仍能对矩阵的元素进行随机存取。 • 稀疏矩阵:设矩阵A中有s个非零元素,若s远远小于矩阵元素的总数(通常认为s≦0.5×m×n),则称A为稀疏矩阵。 数据结构
稀疏矩阵的存储 • 类似于特殊矩阵的存储,可以只存储稀疏矩阵的非零元素。但由于非零元素的分布一般是没有规律的,因此在存储非零元素的同时,还必须同时记下它所在的行和列的位置(i,j)。反之,一个三元组(i,j,aij)唯一确定了矩阵A的一个非零元。因此,稀疏矩阵可由表示非零元素的三元组及其行列数唯一确定。 • 下列三元组表((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 maxsize 12500 typedef struct{ int i,j; datatype v; }triple; typedef struct{ triple data[maxsize + 1];//data[0]未用 int m,n,t; //t为非零元素的个数,即线性表的长度 }tripletable; 数据结构
显然,在三元组顺序表中容易从给定的行列号(i,j)找到对应的矩阵元素。首先按行号i在顺序表中进行“有序”搜索,找到相同的i之后再按列号进行有序搜索,若在三元组顺序表中找到行号和列号都和给定值相同的元素,则其中的非零元值即为所求,否则为矩阵中的零元。显然,在三元组顺序表中容易从给定的行列号(i,j)找到对应的矩阵元素。首先按行号i在顺序表中进行“有序”搜索,找到相同的i之后再按列号进行有序搜索,若在三元组顺序表中找到行号和列号都和给定值相同的元素,则其中的非零元值即为所求,否则为矩阵中的零元。 • 同一行的下一个非零元即为顺序表中的后继,搜索同一列中下一个非零元稍微麻烦些,但由于顺序表是以行号为主序有序的,则在依次搜索过程中遇到的下一个列号相同的元素即为同一列的下一个非零元。 数据结构
讨论当以三元组顺序表表示稀疏矩阵时,如何进行“转置”运算。讨论当以三元组顺序表表示稀疏矩阵时,如何进行“转置”运算。 • 一个m×n的矩阵A,它的转置B是一个n×m的矩阵,且a[i][j]=b[j][i],0≦i<m,0≦j<n,即A的行是B的列,A的列是B的行。 数据结构
由于A的列是B的行,因此,按a.data的列序转置,所得到的转置矩阵B的三元组表b.data必定是按行优先存放的。由于A的列是B的行,因此,按a.data的列序转置,所得到的转置矩阵B的三元组表b.data必定是按行优先存放的。 • 按这种方法设计的算法,其基本思想是:对A中的每一列col(0≦col≦n-1),通过从头至尾扫描三元表a.data,找出所有列号等于col的那些三元组,将它们的行号和列号互换后依次放入b.data中,即可得到B的按行优先的压缩存储表示。 • Status TransposeSMatrix(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].e =M.data[p].e; ++q; } } return OK; } // TransposeSMatrix 时间复杂度为O(M.nu* M.tu) 数据结构
一般传统矩阵的转置算法为: for(row=0;row<=mu-1;++row) for(col=0;col<=nu-1;++col) t[col][row]=m[row][col]; 其时间复杂度为O(M.nu*M.mu)。 • 当非零元素的个数tu和mu*nu同数量级时,算法transmatrix的时间复杂度为O(mu*nu2)。 • 三元组顺序表虽然节省了存储空间,但时间复杂度比一般矩阵转置的算法还要复杂因此,此算法仅适用于t<<m*n的情况。 数据结构
快速转置算法 • 快速转置算法按a.data中三元组的次序进行转置,并将转置后的三元组放入b中恰当的位置。 • 快速转置算法首先在转置前求出矩阵A的每一列col(即B中每一行)的第一个非零元转置后在b.data中的正确位置cpot[col](0≤col<a.nu),然后在对a.data的三元组依次作转置时,只要将三元组按列号col放置到b.data[cpot[col]]中,之后将cpot[col]内容加1,以指示第col列的下一个非零元的正确位置。 • 为了求得位置向量cpot,只要先求出A的每一列中非零元个数num[col],然后利用下面的递推公式即可确定cpot: cpot[1]=1 cpot[col]=cpot[col-1]+num[col-1] 2<=cpl<=a.nu • 如 数据结构
Status FastTransposeSMatrix(TSMatrix M, TSMatrix &T) { T.mu = M.nu; T.nu = M.mu; T.tu = M.tu; if (T.tu) { for (col=1; col<=M.nu; ++col) num[col] = 0; for (t=1; t<=M.tu; ++t) // 求 M 中每一列所含非零元的个数 ++num[M.data[t].j]; cpot[1] = 1; // 求 M 中每一列的第一个非零元在 b.data 中的序号 for (col=2; col<=M.nu; ++col) cpot[col] = cpot[col-1]+num[col-1]; for (p=1; p<=M.tu; ++p) { 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]; } // for } // if return OK; } // FastTransposeSMatrix • 算法的时间复杂度为O(M.nu+M.tu).当非零元素的个数tu和mu*nu同数量级时,算法Fasttransmatrix的时间复杂度为O(M.nu*M.tu)。 数据结构
稀疏矩阵的行逻辑链接顺序表 • 由于三元组顺序表以行序为主序存放矩阵的非零元,则为取得第i行的非零元,必须从第一个元素起进行搜索查询。 • 而从上述讨论的矩阵转置算法可见,在算法过程中计算得到的cpot[col]中的值实际上起到了一个“指示矩阵中每一行的第一个非零元在三元组表中的序号”的作用,因此如果在建立稀疏矩阵的三元组顺序表的同时将这个信息固定在存储结构中,将便于随机存取稀疏矩阵中任意一行的非零元素。这种表示方法为“行逻辑链接”的顺序表。 • #define MAXRC 100 typedef struct{ Triple data[MAXSIZE +1]; int rpos[MAXRC +1];//各行第一个非零元素的位置表 int mu,nu,tu; }RLSMATRIX 数据结构
稀疏矩阵的相乘 • 设Q=M*N,其中,M是m1*n1矩阵,N是m2*n2矩阵。则当n1=m2时有: for(i=1;i<=m1;++i) for(j=1;j<=n2;++j){ q[i][j]=0 for(k=1;k<=n1;++k) q[i][j]+=m[i][k]*n[k][j]; } 此算法的复杂度为O(m1*n1*n2)。 • 对于稀疏矩阵,只要对M.data中的每个元素(i,k,M(i,k))找到N.data中所有相应的元素(k,j,N(k,j))相乘后累加即可。因此需要第k行的所有元素信息,在稀疏矩阵的行逻辑链接顺序表中,N.rpos为我们提供了有关的信息。 • 稀疏矩阵相乘{ Q初始化;ifQ是非零矩阵 { for (arow=1; arow<=M.mu; ++arow) // 处理M的每一行 { ctemp[] = 0; // 累加器清零 计算 Q 中第 arow 行的积并存入 ctemp[] 中; 将 ctemp[] 中非零元压缩存储到 Q.data;} // for arow} // if } 数据结构
Status MultSMatrix(RLSMatrix M, RLSMatrix N, RLSMatrix &Q) { if (M.nu != N.mu) return ERROR; Q.mu = M.mu; Q.nu = N.nu; Q.tu = 0; // Q初始化 if (M.tu*N.tu != 0) { // Q是非零矩阵 for (arow=1; arow<=M.mu; ++arow) { // 处理M的每一行 for (l=1; l<=N.nu; ++l) ctemp[l] = 0; // 当前行各元素累加器清零 Q.rpos[arow] = Q.tu+1; if (arow<M.mu) tp=M.rpos[arow+1]; else tp=M.tu+1; for (p=M.rpos[arow]; p<tp;++p) { // 对当前行中每一个非零元 brow=M.data[p].j; // 找到对应元在N中的行号 if (brow < N.mu ) t = N.rpos[brow+1]; else t = N.tu+1; for (q=N.rpos[brow]; q< t; ++q) { ccol = N.data[q].j; // 乘积元素在Q中列号 ctemp[ccol] += M.data[p].e * N.data[q].e; } // for q } // 求得Q中第crow( =arow)行的非零元 for (ccol=1; ccol<=Q.nu; ++ccol) // 压缩存储该行非零元 if (ctemp[ccol]) { if (++Q.tu > MAXSIZE) return ERROR; Q.data[Q.tu]i=(arow,ccol,ctemp[ccol]); } // if } // for arow } // if return OK; } // MultSMatrix 数据结构
上述算法中,累加器ctemp初始化的时间复杂度为O(M.mu*N.nu),求Q的所有非零元的时间复杂度为O(M.tu*N.tu/N.mu)(N.tu/N.mu表示N中每一行非零元个数的平均值),对Q进行压缩存储的时间复杂度为 O(M.mu*N.nu)。则总的时间复杂度为O(M.mu*N.nu)+O(M.tu*N.tu/N.mu)+O(M.mu*N.nu)= O(M.mu*N.nu+M.tu*N.tu/N.mu)。 • 若 M 是 m 行 n 列的稀疏矩阵,N 是 n 行 p 列的稀疏矩阵,则M中非零元的个数 M.tu = qm×m×n,N中非零元的个数 N.tu = qn×n×p,此时上述算法的时间复杂度就是O(m×p×(1+nqmqn)) ,当qm<0.05 和qn<0.05 及 n<1000时,上述算法的时间复杂度就相当于O(m×p),显然,这是一个相当理想的结果。如果事先能估算出所求乘积矩阵 Q 非是稀疏矩阵,也可设它的存储结构为二维数组,此时的矩阵相乘算法更为简单。 数据结构
稀疏矩阵的十字链表 • 当稀疏矩阵中的非零元素个数和位置在操作过程中变化较大时,不宜采用顺序存储结构来表示三元组的线性表。因为这会引起数据元素在线性表中的移动。此时宜采用链式存储结构。 • 十字链表是稀疏矩阵的链式存储中一种较好的存储方法,在该方法中,每一个非零元用一个结点表示,结点中除了表示非零元所在的行、列和值的三元组(i,j,v)外,还增加了两个链域:行指针域(right),用来指向本行中下一个非零元素;列指针域(down),用来指向本列中下一个非零元素。 • 此时,稀疏矩阵中同一行的非零元通过向右的right指针链接成一个带头结点的链表。同一列的非零元通过down指针链接成一个带头结点的链表。因此,每个非零元既是第i行链表中的一个结点,又是第j列循环链表中的一个结点,相当于处在一个十字交叉路口,故称此种链表为十字链表。 数据结构
typedef struct OLNode{ int i,j; ElemType e; struct OLNode *right, *down; }OLNode; *OLink typedef struct{ OLink *rhead, *chead; int mu,nu,tu; }CrossList; 数据结构
Status CreateSMatrix_OL (CrossList &M) { if(M) free(M); scanf(&m,&n,&t); M.mu=m; M.nu=n; M.tu=t; if (!(M.rhead = (OLink *)malloc((m+1)*sizeof(OLink)))) return ERROR; if (!(M.chead = (OLink *)malloc((n+1)*sizeof(OLink)))) return ERROR; for(int a=1;a<=m;a++) M.rhead[a]=NULL; for(int b=1;b<=n;b++) M.chead[b]=NULL; for ( int c=1; c<=t; c++) { // 按任意次序输入非零元 scanf(&i,&j,&e); if (!(p = (OLNode *)malloc(sizeof(OLNode)))) return ERROR; p->i=i; p->j=j; p->e=e; // 新结点 if (M.rhead[i] == NULL || M.rhead[i]->j > j) { p->right = M.rhead[i]; M.rhead[i]= p; } else { // 寻查在行表中的插入位置 for (q=M.rhead[i]; (q->right) && (q->right->j<j); q=q->right); p->right = q->right; q ->right = p; } // 完成行插入 if (M.chead[j] == NULL || M.chead[j]->i > i) { p->down = M.chead[j]; M.chead[j]= p; } else { // 寻查在列表中的插入位置 for ( q=M.chead[j]; (q->down) && q->down->i <i; q = q->down ); p->down = q->down; q->down = p; } // 完成列插入 } // for return OK; } // CreateSMatrix_OL O(t*max(m,n)) 数据结构
稀疏矩阵的相加(A=A+B) • 当稀疏矩阵用三元组表进行相加时,有可能出现非零元素的位置变动,这时候,不宜采用三元组表作存储结构,而应该采用十字链表较方便。 • 假设原来A和B都用十字链表作存储结构,现要求将B中结点合并到A中,合并后的结果有四种可能: 1)结果为aij+bij; 改变结点的e域值 2)aij(bij=0); 不变 3)bij(aij=0); 改变结点的e域值 4)0。 删除一个结点 • 那么整个运算过程可以从矩阵的第一行起逐行进行。对每一行都从行表头出发分别找到A和B在该行中的第一个非零元结点后开始比较,然后按上述四种不同情况分别处理。 设pa和pb分别是指向A和B的十字链表中行值相同的两个结点,则4种情况描述为: 1)pa->j==pb->j 且pa->e+pb->e≠0,则只要将aij+bij的值送到pa所指结点的值域中即可,其他所有域的值都不变化。 2)pa->j<pb->j且pa->j≠0,则只要将pa指针往右推进一步,并重新加以比较即可。 3)pa->j>pb->j或pa->j=0,则需在A矩阵的链表中插入pb所指结点。 4)pa->j==pb->j且pa->e+pb->e=0,则需要在A矩阵的链表中删除pa所指的结点。这时,需改变同一行中前一结点的rptr域值,以及同一列中前一结点的cptr域值。 数据结构
辅助变量的引入: • pre指针:在A的行链表上指示pa所指结点的前驱结点的指针; • hl指针数组:指向A的每一列的当前结点的指针。 数据结构
A=A+B的算法流程 (1)初始:pa和pb分别指向A和B的第一行的第一个非零元素的结点,即, pa=A.rhead[1]; pb=B.rhead[1]; pre = NULL; 并令hl初始化 for(i = 1; i <= A.nu; i++) hl[i] = A.chead[j]; (2)重复本步骤,依次处理本行结点,直到B的本行中无非零元素的结点,即pb=NULL为止: ①若pa=NULL或pa->j>pb->j(pb->e == 0) (即A的这一行中非零元素已处理完),在A中插入一个pb所指结点的复制结点。设新结点的地址为p,则: • A的行链表中的指针作如下变化: if(pre == NULL) A.rhead[p->i] = p; else pre->right = p; p->right = pa; pre = p; • A的列链表中的指针也要相应地改变。首先从hl[p->j]开始找到新结点在同一列中的前驱结点,并让hl[p->j]指向它,然后在列链表中插入结点p: if(!A.chead[p->j] || A.chead[p->i]>p->i){ p->down = A.chead[p->j]; A.chead[p->j] = p;} else{ p->down = hl[p->j]->down; hl[p->j]->down = p;} hl[p->j] = p; ②若pa!=NULL&&pa->j<pb->j,则令pa指向本行下一个非零元素的结点,即 pre = pa; pa = pa->right; ③若pa->j == pb->j, • 若pa->e + pb->e != 0,则将B中当前结点的值加到A中当前结点上,即pa->e += pb->e • 若pa->e + pb->e == 0,则删除A中该结点,即 if(pre == NULL) A.rhead[pa->i] = pa->right; else{pre->right = pa->right;} p = pa; pa = pa->right; 同时,列链表中的指针也要相应地改变。首先从hl[p->j]开始找到新结点在同一列中的前驱结点,并让hl[p->j]指向它,然后作如下修改: if(A.chead[p->j] == p) A.chead[p->j] = hl[p->j] = p->down; else{hl[p->j]->down = p->down;} free(p) (3)若本行不是最后一行,则令pa和pb指向下一行的第一个非零元素的结点,否则结束。 数据结构
广义表 • 广义表(Lists):是线性表的推广,其元素允许是其自身,如L = (a, L)。 • 广义表一般记作LS=(a1,a2,a3,…,an)。其中,LS是广义表的名字,n为它的长度。可以是单个元素,也可以是广义表,分别称为广义表LS的原子和子表。习惯上,一般用大写字母表示广义表,小写字母表示原子。 • 当广义表非空时,称第一个元素a1为LS的表头(head),称其余元素组成的表(a2,a3,…,an)为LS的表尾(tail)。 数据结构
例: (1)A=( ),A为空表,长度为0。 (2)B=(e),B是长度为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)))。 (5)E=(a,E),是长度为2的广义表,第一项为原子,第 二项为它本身。相当于一个无限的列表 E=(a,(a,(a,…)))。 • 注意:列表()和(())不同。前者为空表,长度为0;后者长度为1,其表头、表尾均为空表()。 数据结构