710 likes | 864 Views
第五章 数组和广义表. —— 数组. 第五章 数组和广义表. ☞ 5.1 数组定义 5.2 数组的顺序表示和实现 5.3 矩阵的压缩存储 5.4 广义表定义 5.5 广义表存储. 数组和广义表是 扩展 的线性表,表中的数据元素本身也是一个数据结构. 例如:二维数组 A = (a 1 , a 2 , …… a n ) 其中 a i = (a 1i , a 2i , …… a mi ) A=((a 11 …… a m1 ), (a 12 …… a m2 ) …… (a 1n …… a mn )). 每个 a i 是一个列向量形式的线性表 或者
E N D
第五章 数组和广义表 ——数组
第五章 数组和广义表 ☞5.1数组定义 5.2 数组的顺序表示和实现 5.3 矩阵的压缩存储 5.4 广义表定义 5.5 广义表存储
数组和广义表是扩展的线性表,表中的数据元素本身也是一个数据结构数组和广义表是扩展的线性表,表中的数据元素本身也是一个数据结构 例如:二维数组 A = (a1, a2, …… an ) 其中ai = (a1i, a2i, …… ami ) A=((a11…… am1), (a12…… am2 ) …… (a1n…… amn)) 每个ai是一个列向量形式的线性表 或者 每个ai是一个行向量形式的线性表
a11 a12 … a1n a21 a22 … a2n ………… am1 am2 … amn (1≤i≤m, 1≤j≤n) Am×n = 同理,一个 n 维数组类型可以定义为其数据元素为n-1维数组类型的一维数组类型
对于任一数组元素aij, 在其所在行和所在列各有一个前趋和后继,那么二维数组还是不是线性结构?? 从这一点上看,二维数组似乎不是线性结构,但二维数组的每一行、列均为定长(长度分别为n、m)的线性表(一维数组), 这样二维数组可看成长度为m、每个数据元素均为定长线性表(一维数组)的线性表。 或者是:长度为n、每个数据元素均为定长(m)线性表的线性表。
数组的定义: ADT Array { 数据对象: ji = 0,...,bi-1, i=1,2,..,n , bi是数组第i维的长度 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) // 操作结果:若维数n和各维长度合法,则构造相应的数组A,并返回OK。 DestroyArray (&A) //操作结果:销毁数组A。 Value (A,&e,index1,...,indexn) //操作结果:若各下标不超界,则e赋值为所指定的A的元素值,并返回OK。 Assign (&A,e,index1,...,indexn) //操作结果:若下标不超界,则将e的值赋给所指定的A的元素,并返回OK。 } //Array 数组一旦被定义,它的维数和维界就不再改变,因此,除了结构的初始化和销毁之外,数组只有存取元素和修改元素值的操作
第五章 数组和广义表 5.1数组定义 ☞5.2 数组的顺序表示和实现 5.3 矩阵的压缩存储 5.4 广义表定义 5.5 广义表存储
数组的顺序存储 以二维数组为例,二维数组一般有两种方法来存储: 1、按行优先顺序存储 2、按列优先顺序存储
a00 a01 按行序为主序存放 0 1 ……. a0n-1 a00 a01 …….. a0 n-1 n-1 a10 n a10 a11 …….. a1n-1 a11 …….. …………………. a1 n-1 am-1 0 am-1 1 …….. am-1 n-1 ………. am-1 0 am-1 1 …….. m*n-1 am-1 n-1 Loc(aij)= Loc(a00)+[i*n+j]*c
a00 a10 ……. am-10 a00 a01……..a0 n-1 a01 a10 a11 ……..a1 n-1 a11 …….. …………………. am-11 am-1 0am-1 1……..am-1 n-1 ………. a0n-1 a1n-1 …….. am-1n-1 0 按列序为主序存放 1 m-1 m Loc(aij)=Loc(a00)+[j*m+i]*c m*n-1
数组的运算: 由于:数组一旦被定义,它的维数和维界(m,n的值)就不会再改变;所以,对于数组的运算,通常有两个: 1、给定下标,存取相应的数据元素; 2、给定下标,修改相应的元素值; 这两个运算在内部实现时,都需要计算出给定元素的实际存储地址,故 计算给定元素的地址是最基本的运算。
【例2】设数组a[1…60, 1…70]的基地址为2048,每个元素占2个存储单元,若以列序为主序顺序存储,则元素a[32,58]的存储地址为。 【例1】一个二维数组A,行下标的范围是1到6,列下标的范围是0到7,每个数组元素用相邻的6个字节存储,存储器按字节编址。那么,这个数组占用个字节。 答:LOC(a32,58)= 2048+[(58-1)*(60-1+1)+32-1)]*2 = 8950 答:m*n*C=6*8*6=48*6=288
由二维数组的计算公式推广可得到n 维数组元素的定位 高维下标其下各低维维界 维界确定就是常数 计算各个元素存储位置的时间相等, 存取数组中任一元素的时间也相等 —随机存储结构
Status DestroyArray(Array &A){ //销毁数组A if(!A.base)return ERROR; free(A.base); A.base=NULL; if!(A.bounds) return ERROR; free(A.bounds); A.bounds=NULL; if!(A.constants) return ERROR; free(A.constants); A.constants=NULL; return OK;} Status Locate(Array A,va_list ap,int &off){ //若ap指示的各下标值合法,则求出该元素在A中相对地址off off=0; for(i=0;i<A.dim; ++i) { ind =va_arg(ap,int); if(ind<0||ind>=A.bounds[i]) return OVERFLOW; off+=A.constants[i]*ind; } return OK; }
Status Value(Array A,ElemType &e,…) { //A是n维数组,e为元素变量,随后是n个下标值。 //若各下标不超界,则e赋值为所指定的A的元素值,并返回OK. va_start(ap,e); if((result=Locate(A,ap,off))<=0) return result; e=*(A.base+off); return OK;} Status Assign(Array &A,ElemType e,…) { //A是n维数组,e为元素变量,随后是n个下标值。 //若各下标不超界,则将e的值赋给所指定的A的元素值,并返回OK. va_start(ap,e); if((result=Locate(A,ap,off))<=0) return result; *(A.base+off) = e; return OK;}
第五章 数组和广义表 5.1数组定义 5.2 数组的顺序表示和实现 ☞5.3 矩阵的压缩存储 5.4 广义表定义 5.5 广义表存储
若矩阵中 非0元素呈某种规律分布,或存在大量 0元素 ,或存在大量值相同的元素,为节省存储空间,可对这类矩阵压缩存储: 即:为 多个相同的非0元素 只分配一个存储空间,对 0元素 不分配存储空间。 若 值相同的元素 或 0元素 在矩阵中的分布有一定的规律,我们称此矩阵为特殊矩阵;反之,称为稀疏矩阵。
所谓特殊矩阵是指非零元素或零元素的分布有一定规律的矩阵,下面我们讨论几种特殊矩阵的压缩存储。所谓特殊矩阵是指非零元素或零元素的分布有一定规律的矩阵,下面我们讨论几种特殊矩阵的压缩存储。 特殊矩阵 • 对称矩阵 • 三角矩阵 • 对角矩阵
12 3 4 2 5 6 7 3 6 8 9 4 7 9 10 1、对称矩阵 在一个 n 阶方阵 A 中,若元素满足下述性质: aij=aji, 1 ≤ i,j ≤ n 则称A为对称矩阵。 A=
a11 a21 a22…… an1 an2 … ann sa 0 1 2 ......... ........ n(n+1)/2-1 对称矩阵中的元素关于主对角线对称,故只要存储矩阵中上三角或下三角中的元素,让每两个对称的元素共享一个存储空间,这样,能节约近一半的存储空间。不失一般性,我们按“行优先顺序”存储主对角线(包括对角线)以下的元素 a11 a12 … a1n a21 a22 … a2n ………… an1 an2 … ann
a11 a12 … a1n a21 a22 … a2n ………… an1 an2 … ann 在这个下三角矩阵中,第i行恰有i个元素(i>1),元素总数为:n(n+1)/2 因此,我们可以按从上到下、从左到右将这些元素存放在一个向量sa[n(n+1)/2]中。为了便于访问对称矩阵A中的元素,我们必须在aij和sa[k]之间找一个对应关系。 若已知基地址是sa[0],那么loc(aij)=?
若 i≥j ,则aij在下三角形中。 aij之前的i-1行(从第1行到第i-1行)一共有1+2+…+(i-1)=i(i-1)/2个元素,在第i行上,aij之前有j-1个元素(即ai1, ai2,…, ai j-1),因此有: k=i*(i-1)/2+j-1 0≤ k<n(n+1)/2 若 i<j ,则aij是在上三角矩阵中。因为aij =aji,所以只要交换上述对应关系式中的 i 和 j 即可得到: k=j*(j-1)/2+i -1 0≤k<n(n+1)/2
sa[0]+ i * (i-1)/2+j -1, i≤j sa[0]+ j * (j-1)/2+i -1 , i>j loc(aij) =
2、三角矩阵 以主对角线划分,三角矩阵有上三角和下三角两种。 上三角矩阵的下三角(不包括主对角线)中的元素均为常数。 下三角矩阵正好相反,它的主对角线上方均为常数 a11 a12 … a1n a11 c … c c a22 … a2n a21 a22 … c ………………….. …………….. c c …… c …… ann an1 … ann 上三角矩阵 下三角矩阵
三角矩阵中的重复元素 c 可共享一个存储空间,其余的元素正好有n(n+1)/2个,因此,三角矩阵可压缩存储到向量sa[n(n+1)/2 +1]中,其中c存放在向量的最后一个分量中。 a11 a12 … a1n a11 c … c c a22 … a2n a21 a22 … c ………………….. …………….. c c …… c …… ann an1 … ann
3、对角矩阵 对角矩阵中,所有的非零元素集中在以主对角线为中心的带状区域中,即除了主对角线和主对角线相邻两侧的若干条对角线上的元素之外,其余元素皆为零。下图给出了一个三对角矩阵 a00 a01 a10a11 a12 a21a22 a23 …. ….. …. an-2 n-3an-2 n-2 an-2 n-1 an-1 n-2an-1 n-1 0 0
非零元素仅出现在 主对角线(aii,0≦i≦n-1)上 紧邻主对角线上面的那条对角线上(ai i+1,0≦i≦n-2) 和紧邻主对角线下面的那条对角线上(ai+1 i,0≦i≦n-2)。 显然,当∣i -j∣>1时,元素aij=0。 由此可知,一个k对角矩阵(k为奇数)A是满足下述条件的矩阵:若∣i - j∣>(k-1)/2,则元素 aij=0。 对角矩阵可按 行优先顺序 或 对角线的顺序,将其压缩存储到一个向量中,并且也能找到每个非零元素和向量下标的对应关系。
在三对角矩阵里除满足条件i=0时,j=0、1(第0行),或i=n-1时,j=n-2、n-1(第n-1行)或1≤i<n-1时, j=i-1、i、i+1的元素aij外,其余元素都是零。 对这种矩阵,我们也可按行优序为主序来存储。除第0行和第n-1行是2个元素外,每行的非零元素都要是3个,因此,需存储的元素个数为3n-2。 K=0 1 2 3 4 5 … … 3n-4 3n-3
K= 0 1 2 3 4 5 … … 3n-4 3n-3 数组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)]*C = LOC(0,0)+(2i+j)*C
上述的各种特殊矩阵,其非零元素的分布都是有规律的,因此总能找到一种方法将它们压缩存储到一个向量中,并且一般都能找到矩阵中的元素与该向量的对应关系,通过这个关系,仍能对矩阵的元素进行随机存取。上述的各种特殊矩阵,其非零元素的分布都是有规律的,因此总能找到一种方法将它们压缩存储到一个向量中,并且一般都能找到矩阵中的元素与该向量的对应关系,通过这个关系,仍能对矩阵的元素进行随机存取。
0 0 -3 0 0 15 12 0 0 0 18 0 9 0 0 24 0 0 0 0 0 0 0 –7 0 0 14 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 M = 稀疏矩阵 什么是稀疏矩阵? 简单说,假设m行n列的矩阵含t个非零元素,则称 为稀疏因子,通常认为 0.05 的矩阵为稀疏矩阵
唯一确定 三元组 + 行数 + 列数 稀疏矩阵 在存储稀疏矩阵时,为了节省存储单元,只存储非零元的值。 用一个三元组(i, j, aij) 确定矩阵A的一个非零元。 行号 值 列号
0 0 -3 0 0 15 12 0 0 0 18 0 9 0 0 24 0 0 0 0 0 0 0 –7 0 0 14 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 M= 【例】三元组 (1,3,-3)、(1,6,15)、(2,1,12)、 (2,5,18)、(3,1,9)、(3,4,24)、(4,6,-7)、(5,3,14)) 加上“行数为7,列数为6,非0元个数为8”对应的矩阵M是??
稀疏矩阵的存储方式之一 ——三元组顺序表 将稀疏矩阵中非0元素的三元组按行优先(或按列优先)顺序排列,将得到数据元素为三元组的线性表。
i j v 1 3 -3 1 6 15 2 1 12 2 5 18 31 9 3 4 24 4 6 -7 5 3 14 a 0 0 -3 0 0 15 12 0 0 0 18 0 9 0 0 24 0 0 0 0 0 0 0 –7 0 0 14 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 data[ ] mu nu M= tu 若定义变量 a 存放矩阵M的三元组顺序表: TSMatrix a; 存放矩阵M的数组是a.data[ maxsize +1]
三元组顺序表 的类型定义如下: typedef struct { int i , j ; ElemType e ; } Triple ;//三元组 #define maxsize 12500 typedef struct { int mu, nu, tu; /*mu行数,n u列数,tu非0元个数*/ Triple data[maxsize+1] ;//data[0]未用 } TSMatrix; //三元组顺序表
0 0 -3 0 0 15 12 0 0 0 18 0 9 0 0 24 0 0 0 0 0 0 0 –7 0 0 14 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 12 9 0 0 0 0 0 0 0 0 0 0 0 -3 0 0 0 14 0 0 0 0 24 0 0 0 0 0 18 0 0 0 0 0 15 0 0 -7 0 0 0 B = A = 【例】求稀疏矩阵A的转置矩阵B 一个m×n的矩阵A,它的转置B是一个n×m的矩阵,且a[i][j]=b[j][i],1≤i≤m,1≤j≤n,即A的行是B的列,A的列是B的行。
678 768 1 2 12 1 3 -3 1 3 9 1 6 15 3 1 -3 2 1 12 2 5 18 3 6 14 4 3 24 3 1 9 ? 5 2 18 3 4 24 4 6 -7 6 1 15 6 3 14 6 4 -7 用顺序表存储结构实现矩阵的转置运算 i j e i j e 0 1 2 3 4 5 6 7 8 0 1 2 3 4 5 6 7 8 b.data a.data
将A转置为B • 将矩阵行、列维数互换 • 将每个三元组中的i和j相互调换 • 重排三元组次序,使其中元素以B的行(A的列)为主序
i j v i j v 0 1 2 3 4 5 6 7 8 0 1 2 3 4 5 6 7 8 p p p p p p p p p q q q p p q p p p p p q 6 78 1 2 12 1 3 9 3 1 -3 3 6 14 a b 4 3 24 5 2 18 6 1 15 6 4 -7 方法一:按照b.data中的三元组的次序依次在a.data中找到相应的三元组进行转置 76 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 &T) {//采用三元组表存储表示,求稀疏矩阵M的转置矩阵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; }// TransposeSMtrix 算法的时间复杂度是O(nu·tu),即和M的列数及非零元的个数的 乘积成正比
cpot[1]=1; cpot[col]=cpot[col-1]+num[col-1]; (2col a.nu) col 3 6 7 1 2 4 5 2 num[col] 1 0 2 2 1 0 cpot[col] 方法二:快速转置 即按a.data中三元组次序转置,转置结果放入b中恰当位置 此法关键是要预先确定M中每一列第一个非零元在b.data中位置, 为确定这些位置,转置前应先求得M的每一列中非零元个数 实现:设两个数组 num[col]:表示矩阵M中第col列中非零元个数 cpot[col]:指示M中第col列第一个非零元在b.data中位置 显然有: 5 8 9 1 3 7 8
算法描述: Status FastTransposeSMatrix(TSMatrix M, TSMatrix &T) {//采用三元组顺序表存储表示,求稀疏矩阵M的转置矩阵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) ++num[M.data[t].j]; //求M中每一列非零元个数//求第 col列中第一个非零元在T.data中的序号 cpot[1]=1; 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 • 算法分析:T(n)=O(nu+tu) • 若 tu 与munu同数量级,则T(n)=O(munu)
2.行逻辑链接的顺序表 • 有时为了方便某些矩阵运算,我们在按行优先存储的三元组中,加入一个行表来记录稀疏矩阵中每行的非零元素在三元组表中的起始位置。当将行表作为三元组表的一个新增属性加以描述时,我们就得到了稀疏矩阵的另一种带行链接信息的顺序存储结构:行逻辑链接的顺序表。其类型描述如下: typedef struct{ triple data[MAXSIZE+1];//非零元三元组表 int rpos[MAXRC+1];//各行第一个非零元的位置表 int mu,nu,tu;//矩阵的行数、列数和非零元个数 }RLSMatrix; 下面讨论两个稀疏矩阵相乘的例子,容易看出这种表示方法的优越性。
矩阵乘法 11 -6 -1 0 -4 6
两个矩阵相乘的经典算法也是大家所熟悉的。若设 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)。 • 乘积矩阵Q中元素 1 ≤i≤m1, 1≤j≤n2 • 在经典算法中,不论M(i,k)和N(k,j)的值是否为零。都要进行依次乘法运算,而实际上,这两者有一个值为零时,其乘积也为零。因此,在对稀疏矩阵进行运算时,应免去这种无效操作,换句话说,只要在M.data和N.data中找到相应的各对元素(即M.data中的j值和N.data中的i值相等的各对元素)相乘即可。
则Q=M*N为: 假设M和N分别为: 0 2 1 0 -2 4 0 0 0 6 -1 0 0 4 • 0 0 5 • 0 -1 0 0 • 2 0 0 0 M= N= Q= 它们的三元组、和分别为: i j v i j v i j v 1 1 3 1 2 2 1 2 6 1 4 5 2 1 1 2 1 -1 3 2 -1 3 1 -2 3 2 4 3 1 2 3 2 4 q.data m.data n.data