第5章 数组和广义表
DESCRIPTION
第5章 数组和广义表. 数组和广义表的特点: 特殊的线性表. 元素的值并非原子类型,可以再分解,表中 元素也是一个线性表(即线性表的推广)。. 第5章 数组和广义表. 5.1 数组的定义 5.2 数组的顺序表示和实现 5.3 矩阵的压缩存储 5.4 广义表的定义 5.5 广义表的存储结构. 5.1 数组的定义. 数组: 由一组名字相同、下标不同的同类型的元素 组成. 数组特点 数组结构固定, 下标一般具有 固定的上界和下界 数据元素 具有 统一的类型 数组运算 给定一组下标, 取 相应的数据元素. - PowerPoint PPT PresentationTRANSCRIPT
第 5 章 数组和广义表
元素的值并非原子类型,可以再分解,表中 元素也是一个线性表(即线性表的推广)。
数组和广义表的特点:特殊的线性表
5.1 数组的定义5.2 数组的顺序表示和实现5.3 矩阵的压缩存储5.4 广义表的定义5.5 广义表的存储结构
第 5 章 数组和广义表
5.1 数组的定义 数组: 由一组名字相同、下标不同的同类型的元素 组成 数组特点
• 数组结构固定 , 下标一般具有固定的上界和下界• 数据元素具有统一的类型
数组运算• 给定一组下标,取相应的数据元素 .• 给定一组下标,修改数据元素的值 .
数组的处理比其它复杂的结构要简单
与高级语言中数组的区别:
1 、本章所讨论的数组是一种数据结构,而高级语言
中数组是一种数据类型。
2 、高级语言中的数组是顺序结构;而本章的数组
既可以是顺序的,也可以是链式结构,用户可根
据需要选择。
二维数组的特点:
一维数组的特点: 1 个下标, ai 是 ai+1 的直接前驱
2个下标,每个元素 aij 受到两个关系(行关系和列关系)的约束:
一个 m×n 的二维数组可以看成是由 m 行的一维数组组成或由 n 列的一维数组组成。
A0
A1
.
.
Am-1
Ai=(ai0, ai1, …, ai,n-1)
i=0,1,2…,m-1
1n1,m1,1m1,0m
1n1,1110
1n0,0100
mn
aaa
aaaaaa
A
)aa(a
)aa(a)aa(a
A
1n1,m1,1m1,0m
1n1,1110
1n0,0100
mn
5.1 数组的定义
1n1,m
1n1,
1n0,
1,1m
11
01
1,0m
10
00
mn
a
aa
a
aa
a
aa
A
B0 B1 … Bn-1
1n10mn BBBA
二维数组是一个数据元素为线性表的线性表
j1,m
1j
0j
j
a
aa
B
j=0,1,…,n-1
aij(1≤i ≤m-2 , 1 ≤j ≤n-2) 有两个前驱结点 ai,j-1 和 ai-1,j
两个后继结点 ai,j+1 和 ai+1,j
a00 没有前驱结点 , 称之为开始结点 .
am-1,n-1 没有后继结点 , 称之为终端结点 .
第 0 行的元素 a0j(j=1,…,n-1)
第 0 列的元素 ai0(i=1,…,m-1)
只有一个前驱
只有一个后继
我们可以把二维数组中的元素 aij 看成是属于两个线性表 :
即第 i 行的线性表 Ai 和第 j 列的线性表 Bj
第 m-1 行的元素 am-1,j(j=1,…,n-2)
第 n-1 列的元素 ai,n-1(i=1,…,m-2)
同理,三维数组 Am×n × l 中每个元素属于三个线性表,每个元素
最多有三个前驱和三个后继。
ai1,i2,i3 前驱: ai1-1,i2,i3 , ai1,i2-1,i3 , ai1,i2,i3-1
后继: ai1+1,i2,i3 , ai1,i2+1,i3 , ai1,i2,i3+1
推而广之 ,一个 n 维数组可以看成是由若干个 n - 1 维数组组成
的线性表,在 n 维数组 Ab1 ×b2 ×… ×bn 中 ,
每个元素 ai1,i2,…,in(0 ≤ij ≤bi-1,j=1,2,…,n) 属于 n 个线性表 ,
每个元素最多有 n 个前驱和 n 个后继。
ai1,i2,…,in 前驱: ai1-1,i2,…,in , ai1,i2-1,…,in …, , , ai1,i2,…,in-1
后继: ai1+1,i2,…,in , ai1,i2+1,…,in …, , ai1,i2,…,in+1
数据对象 : D = {aij | aij ElemSet , 0≤i≤b1-1, 0 ≤j≤b2-1}
数据关系 : R = { ROW, COL }
ROW = {<ai,j,ai,j+1>| 0≤i≤b1-1, 0≤j≤b2-2}
COL = {<ai,j,ai+1,j>| 0≤i≤b1-2, 0≤j≤b2-1}
二维数组的 定义 :2b1bA
InitArray(&A, n, bound1, ..., boundn) 操作结果:若维数 n 和各维长度合法,则构造相应 的数组 A ,并返回OK 。
基本操作:
DestroyArray(&A) 操作结果:销毁数组 A 。
Value(A, &e, index1, ..., indexn)// 取数组元素 初始条件: A 是 n 维数组, e 为元素变量,随后是 n 个下标值。 操作结果:若各下标不超界,则 e 赋值为所指定的 A 的元素值,并返回 OK 。
基本操作:
Assign(&A, e, index1, ..., indexn)// 修改数组元素 初始条件: A 是 n 维数组, e 为元素变量,随后是 n 个下标值。 操作结果:若下标不超界,则将 e 的值赋给所指 定的 A 的元素,并返回 OK 。
5.2 数组的顺序存储表示和实现
问题:计算机的存储结构一般是一维的,而 n 维数组(n≥2) 一般是多维的,怎样存放?
解决办法:事先约定按某种次序将数组元素排成一序
列,然后将这个线性序列存入存储器中。例如:在二维数组中,我们既可以规定按行存储,也
可以规定按列存储。若规定好了次序,则数组中任意一个元素的存放地址便有规律可寻,可形成地址计算公式;约定的次序不同,则计算元素地址的公式也有所不同;C 和 PASCAL 中一般采用行优先顺序; FORTRAN 采用
列优先。
按行序为主序存放
am-1,n-1
……..
am-1,1
am-1,0
……….
a1n-1
……..
a11
a10
a0,n-1
…….
a01
a00
a00 a01 …….. a0,n-1
a10 a11 …….. a1,n-1
am-1,0 am-1,1 … am-1,n-1
………………….
01
n-1
m*n-1
n
am-1,n-1
……..
a1,n-1
a0,n-1
……….
am-1,1
……..
a11
a01
am-1,0
…….
a10
a00
a00 a01 …….. a0n-1
a10 a11 …….. a1n-1
am-10 am-11 ……. am-1n-1
………………….
按列序为主序存放
0
1
m*n-1
m-1
m
计算二维数组元素地址的通式
二维数组列优先存储的通式为:LOC(aij)=LOC(a00)+(b1*j+i)*L
则行优先存储时的地址公式为:LOC(aij)=LOC(a00)+(b2*i+j)*L
设一般的二维数组是 A[0..b1-1, 0..b2-1]
1b21,b11,0b1
1b2i,iji0
1b20,00
mn
a.........a...............
a...a...a...............
a.........a
A
计算三维数组元素地址的通式
设一般的三维数组是 A[0..b1-1, 0..b2-1 , 0..b3-1]
按“行优先顺序”存储,其任一元素 Aijk 地址计算函数为:
LOC(aijk)=LOC(a000)+(i*b2*b3+j*b3+k)*L
若是 N 维数组,其中任一元素的地址该如何计算?
LjbjaLOC
Ljjbjbbb
jbbbaLOCaLOC
n
i
n
iknki
nnnn
nnjjj
1
1 10...00
1243
1320...00,...,21
)()(
)......
...()()(
① 开始结点的存放地址(即基地址)② 维数和每维的上、下界;③ 每个数组元素所占用的单元数
其中 Cn=L, Ci-1=bi×Ci , 1<i≤n (递归)
Loc(j1,j2,…jn)=LOC(0,0,…0) +
n
iii
1
jC
5.3 矩阵的压缩存储 在科学与工程计算问题中,矩阵是一种常用的
数学对象,在高级语言编制程序时,将一个矩阵描述为一个二维数组。矩阵在这种存储表示之下,可以对其元素进行随机存取,各种矩阵运算也非常简单。
但是在矩阵中非零元素呈某种规律分布或者矩阵中出现大量的零元素的情况下,占用了许多单元去存储重复的非零元素或零元素,这对高阶矩阵会造成极大的浪费,为了节省存储空间, 我们可以对这类矩阵进行压缩存储:即为多个相同的非零元素只分配一个存储空间;对零元素不分配空间。
并非所有的矩阵都可以压缩
对称矩阵
三角矩阵
稀疏矩阵
5.3.1 特殊矩阵
在一个 n 阶方阵 A 中,若元素满足下述性质: aij=aji 0≤ i,j≤ n-1
1 5 1 3 7 a00
5 0 8 0 0 a10 a 11
1 8 9 2 6 a20 a21 a22
3 0 2 5 1 ………………..
7 0 6 1 3 an-1 0 an-1 1 an-1 2 …an-1 n-1
对称矩阵
1 、对称矩阵
sa[k]
按行序为主序:
a00 a10 a11 a20 a21 a22 … an-1,0 … an-1,n-1
0 1 2 3 4 5 … n(n-1) … n(n+1)/2-1
1 )对称矩阵的存储方式
在对称矩阵中,第 i 行恰有 i+1 个元素,元素总数为:
可以将这些元素存放在一个向量 中
1 、对称矩阵
1n
0i 2
1)n(n1)(i
]12/)1(..0[ nnsa
2 )为了便于访问对称矩阵 A 中的元素,我们必须在 aij
和 sa[k] 之间找一个对应关系。若 i≥j ,则 aij 在下三角形中。 aij 之前的 i 行(从第0 行到第 i-1 行)一共有 1+2+…+i=i*(i+1)/2 个元素,在第 i 行上, aij 之前恰有 j 个元素(即 ai0,ai1,ai2,…,aij-1 ),因此有: k=i*(i+1)/2+j 0≤k≤n(n+1)/2-1 若 i<j ,则 aij 是在上三角矩阵中。因为 aij=aji ,所以只要交换上述对应关系式中的 i 和 j 即可得到: k=j*(j+1)/2+i 0≤k≤n(n+1)/2-1
1 、对称矩阵
2 、三角矩阵
以主对角线划分,三角矩阵有上三角和下三角两种。上三角矩阵中主对角线以下的元素均为常数 (a) 。下三角矩阵正好相反,主对角线以上的元素均为常数
(b) 。 a00 a01 …….. a0,n-1
c a11 …….. a1,n-1
c c c … am-1,n-1
… … … …
(a) 上三角矩阵
a00 c …….. c
a10 a11 c……..c
am-10 am-11 … am-1,n-1
… … … …
(b) 下三角矩阵可以用向量 sa[0..n(n+1)/2] 存储,将常量存入第一或最后一个单元
5.3.2 稀疏矩阵1 、什么是稀疏矩阵?
设矩阵 A 中有 s 个非零元素,若 s 远远小于矩阵元素的总数(即 s<<m×n),则称 A 为稀疏矩阵。
有 s 个非零元素。令 e=s/(m×n),称 e 为矩阵的稀疏因子。通常认为 e≤ 0.05时为稀疏矩阵。
稀疏矩阵的压缩存储方法
一、三元组顺序表
二、行逻辑联接的顺序表
三、 十字链表
存储稀疏矩阵时,为了节省存储单元,使用压缩 存储方法。非零元素的分布一般是没有规律的,因此在存储 非零元素的同时,还必须同时记下它所在的行和 列的位置( i,j)。一个三元组 (i,j,aij) 唯一确定了矩阵 A 的一个非零 元。因此,稀疏矩阵可由表示非零元的三元组及 其行列数唯一确定。
一、三元组顺序表
例如,下列稀疏矩阵
5.3.2 稀疏矩阵
可由三元组表 ((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 )唯一确定 7600070015
00000180
00002400
01400003
0000000
00009120
M=
1
2
3
4
5
6
1 2 3 4 5 6 7
一、三元组顺序表 #define MAXSIZE 12500 typedef struct { int i, j; // 该非零元的行下标和列下标 ElemType e; // 该非零元的值 } Triple; // 三元组类型
typedef struct { Triple data[MAXSIZE+1]; int mu, nu, tu; (mu 行数 ,nu 列数 ,tu 非零元个数 )} TSMatrix; // 稀疏矩阵类型
三元组表表示法:
1 2 12
1 3 9
3 1 -3
3 5 14
4 3 24
5 2 18
6 1 15
6 4 -7
注意:三元组表中的元素按行(或列)排列。
6 6 8
i j e
稀疏矩阵压缩存储的缺点:将失去随机存取功能
00
11
22
33
44
55
66
77
88
0 12 9 0 0 00 0 0 0 0 0
-3 0 0 0 14 00 0 24 0 0 00 18 0 0 0 0
15 0 0 -7 0 0
求转置矩阵算法
028003600070500140
M
00528000000714
3600
T
用常规的二维数组表示时的算法
其时间复杂度为 : O(mu×nu)
for (c=1; c<=nu; ++c)
for (r=1; r<=mu; ++r)
T[c][r] = M[r][c];
三元组表示的转置
(1, 2, 12)
(1, 3, 9 )
(3, 1, -3)
(3, 5, 14)
(4, 3, 24)
(5, 2, 18)
(6, 1, 15)
(6, 4, -7)
(1, 3, -3)
(1, 6, 15)
(2, 1, 12)
(2, 5, 18)
(3, 1, 9)
(3, 4, 24)
(4, 6, -7)
(5, 3, 14)
三元组表
M.data
三元组表
T.data
转置后
0 12 9 0 0 0
0 0 0 0 0 0
-3 0 0 0 14 0
0 0 24 0 0 0
0 18 0 0 0 015 0 0 -7 0 0
M=
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 00 0 0 0 0 0
T=
?
不正确!( 1)每个元素的行下标和列下标互换(即三元组中的 i 和
j 互换);( 2 ) T 的总行数 mu 和总列数 nu 与 M 值不同(互换);( 3)重排三元组内元素顺序,使转置后的三元组也按行 (或列)为主序有规律的排列。上述( 1)和( 2)容易实现,难点在( 3)。
提问:若采用三元组压缩技术存储稀疏矩阵,只要把每个元素的行下标和列下标互换,就完成了对该矩阵的转置运算,这种说法正确吗?
有两种实现方法压缩转置
(压缩 )快速转置
方法 1 :压缩转置
思路:反复扫描 M.data中的列序,从小到大依次进行转置。
6 7 8
1 2 12
1 3 9
3 1 -3
3 6 14 4 3 24
5 2 18
6 1 15
6 4 -7
i j e
0 1 2 3 4 5 6 7
8
M
7 6 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
i j e
0 1 2 3 4 5 6 7 8
T
qppp
pppp
p
qqqq
pppppppp
col=1 col=2
q
Status TransPoseSMatrix(TSMatrix M, TSMatrix &T){//用三元组表存放稀疏矩阵 M,求 M 的转置矩阵 TT.mu=M.nu; T.nu=M.mu; T.tu=M.tu; //nu是列数 ,mu是行数 ,tu是非零元素个数if (T.tu) { q=1; //q 是转置矩阵 T 的结点编号 for(col=1; col<=M.nu; col++) {for(p=1; p<=M.tu; p++) //p 是 M 三元表中结点编号 {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;} //TranposeSMatrix
压缩转置算法描述:
1 、主要时间消耗在查找 M.data[p].j=col的元素,由两重循环完成 : for(col=1; col<=M.nu; col++) 循环次数= nu for(p=1; p<=M.tu; p++) 循环次数= tu所以该算法的时间复杂度为 O(nu*tu) ----即 M 的列数与 M 中非零元素的个数之积
最坏情况: M 中全是非零元素,此时 tu=mu*nu , 时间复杂度为 O(nu2*mu )注:若 M 中基本上是非零元素时,即使用非压缩传统转置算法的时间复杂度也不过是 O(nu*mu)
结论:压缩转置算法不能滥用。
前提:仅适用于非零元素个数很少(即 tu<<mu*nu)的情况。
压缩转置算法的效率分析
方法 2 快速转置
三元组表
M.data
三元组表
T.data
③
(1, 3, -3)
①
(2 ,1, 12)
⑥
(2, 5, 18)
②
(3, 1, 9)
⑧(4, 6, -7)
④
(5, 3, 14)
⑦
(1, 6, 15)
⑤
(3, 4, 24)
(1, 2, 12)
(1, 3, 9 )
(3, 1, -3)
(3, 5, 14)
(4, 3, 24)
(5, 2, 18)
(6, 1, 15)
(6, 4, -7)
思路:依次把 M.data 中的元素直接送入 T.data 的恰当位置上(即M 三元组的 p 指针不回溯)。
关键:怎样寻找 T.data的“恰当”位置?
p1234
q
3
5
如果能预知M 矩阵中每一列 (即 T 的每一行 )的非零元素个数,又能预知每一列第一个非零元素在T.data中的位置 ,则扫描M.data 时便可以将每个元素准确定位。
设计思路:
注意:根据 M.data的特征,每列第一个非零元素必 定先被扫描到。
令: M 中的列变量用 col 表示; num[ col ] :存放 M 中第 col 列中非 0元素个数, cpot[ col ] :存放 M 中第 col 列的第一个非 0元素的位置, (即 T.data 中待计算的“恰当”位置所需参考点)
col 1 2 3 4 5 6
num[col] 2 2 2 1 1 0
cpot[col] 1
规律 : cpot(1) = 1 cpot[col] = cpot[col-1] + num[col-1]
0 12 9 0 0 0
0 0 0 0 0 0
-3 0 0 0 14 0
0 0 24 0 0 0
0 18 0 0 0 015 0 0 -7 0 0
M=
3
col 1 2 3 4 5 6
5 9 8 7
6 6 8
1 2 12
1 3 9
3 1 -3
3 5 14 4 3 24
5 2 18
6 1 15
6 4 -7
i j v
0 1 2 3 4 5 6 7 8
M
6 6 8
1 3 -3
1 6 15
2 1 12
2 5 18 3 1 9
3 4 24
4 6 -7
5 3 14
ppp
pppp
p
4 62 9
753
col 1 2 3 4 5 6
num[col] 2 2 2 1 1 0
cpot[col] 1 3 5 7 8 9
i j v
0 1 2 3 4 5 6 7 8
T
Status FastTransposeSMatrix ( TSMatirx M, TSMatirx &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( i = 1; i <=M.tu; i ++) {col =M.data[ i ] .j ; ++num [col] ;} 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] ;+ + cpot[col] ; } //for } //if return OK; } //FastTranposeSMatrix;
快速转置算法描述:
//M 用顺序存储表示,求 M 的转置矩阵 T
//先统计每列非零元素个数
// 再生成每列首元位置辅助向量表
//p 指向 a.data,循环次数为非 0元素总个数 tu // 查辅助向量表得 q,即 T 中位置
//修改向量表中列坐标值,供同一列下一非零元素定位之用!
1. 与常规算法相比,增加了 2个长度为列长的辅助数组 (num[ ] 和 cpot[ ] )。
快速转置算法的效率分析:
2. 从时间上,此算法用了 4个并列的单循环,而且其中前 3个单循环都是用来产生辅助数组的。
for(col = 1; col <=M.nu; col++) 循环次数= nu; for( i = 1; i <=M.tu; i ++) 循环次数= tu; for(col = 2; col <=M.nu; col++) 循环次数= nu; for( p=1; p <=M.tu ; p ++ ) 循环次数= tu;
该算法的时间复杂度= (nu*2)+(tu*2)=O(nu+tu)
传统转置: O(mu*nu) 压缩转置: O(mu*tu) 压缩快速转置: O(nu+tu)——牺牲空间效率换时 间效率。
小结:
讨论:最坏情况是 tu=nu*mu( 即矩阵中全部是非零元素),而此时的时间复杂度也只是 O(mu*nu),并未超过传统转置算法的时间复杂度。
三元组顺序表又称有序的双下标法,它的特点是,非零元在表中按行序有序存储,因此便于进行依行顺序处理的矩阵运算。然而,若需随机存取某一行中的非零元,则需从头开始进行查找。
二、行逻辑联接的顺序表
#define MAXSIZE 500 typedef struct { Triple data[MAXSIZE + 1]; // 三元组表 int rpos[MAXMN + 1]; // 每一行非零元的位置表 int mu, nu, tu; } RLSMatrix; // 行逻辑链接顺序表类型
二、行逻辑联接的顺序表
0 12 9 0 0 0
0 0 0 0 0 0
-3 0 0 0 14 0
0 0 24 0 0 0
0 18 0 0 0 015 0 0 -7 0 0
M=1 2 12
1 3 9
3 1 -3
3 5 14
4 3 24
5 2 18
6 1 15
6 4 -7
6 6 8
i j e
00
11
22
33
44
55
66
77
88
row 1 2 3 4 5 6
num[row] 2 0 2 1 1 2
rpos[row] 1 3 3 5 6 7
例如:给定一组下标,求矩阵的元素值
ElemType value(RLSMatrix M, int r, int c) {
p=M.rpos[r];// 第 r 行第一个非零值的位置 while (M.data[p].i==r &&M.data[p].j < c)
p++;
if (M.data[p].i==r && M.data[p].j==c)
return M.data[p].e;
else return 0;
} // value
三、 十字链表
用途:方便稀疏矩阵的加减运算;方法:每个非 0 元素占用 5 个域。
right down
eji
同一列中下一非零元素的指针
同一行中下一非零元素的指针
十字链表的特点:①每行非零元素链接成带表头结点的线性链表;②每列非零元素也链接成带表头结点的线性链表。则每个非零元素既是行链表中的一个结点;又是列链表中的一个结点,即呈十字链状。
M.chead
M.rhead
3 0 0 50 -1 0 02 0 0 0
1 1 3 1 4 5
2 2 -1
3 2
^
^^
^ ^
^ ^2
稀疏矩阵的十字链表存储表示
Typedef struct OLNode{
int i,j; // 非零元素下标 ElemType e;
struct OLNode *right, *down;
}OLNode; *Olink;
Typedef struct {
Olink *rhead, *chead;
int mu,nu,tu;
} CrossList;
建立稀疏矩阵(算法 5.4 )Status CreateSMtrix_OL(CrossList &M){
if(M) free(M);
scanf(&m,&n,&t);
M.mu=m; M.nu=n; M.tu=t;
M.rhead=(Olink*)malloc((m+1)*sizeof(Olink)) ; M.chead=(Olink*)malloc((n+1)*sizeof(Olink))
M.rhead[ ]=M.chead[ ]=NULL;
for(scanf(&i,&j,&e); i!=0; scanf(&i,&j,&e)){
p=(OLNode*)malloc(sizeof(OLNode)) ; 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;}
}//end for
Return Ok;
}//CreateSMtrix_OL
4 1 8^ ^
2 3 4
^ ^
输入:m=4,n=31,1,32,2,52,3,44,1,82,1,7
1 1 3
^^
2 1 7
^ ^2 2 5
^ ^
rhead[1]
rhead[2]
rhead[3]
rhead[4]
chead[1] chead[2] chead[3]
3 0 0 50 -1 0 02 0 0 0
矩阵相加算法
A=0 2 0 -10 1 0 20 0 0 4
B=
A=A+B
A=3 2 0 40 0 0 22 0 0 4
对于每一行做如下处理
A.chead
A.rhead
1 1 3 1 4 5
2 2 -1
3 1 2
^
^^
^ ^
^ ^
2 2 1
1 4 -11 2 2
2 4 2
3 4 4
^^
^ ^
^ ^
B.rhead
B.chead
A
Bpa=A.rhead[1];pb=B.rhead[1];pre=NULL;
for(j=1;j<=A.nu;++j) hl[j]=A.chead[j];
pa
pb
hl[1] hl[3] hl[4]hl[2]
if(pa->j<pb->j)
{pre=pa;pa=pa->right;}
A.chead
A.rhead
1 1 3 1 4 5
2 2 -1
3 1 2
^
^^
^ ^
^ ^
2 2 1
1 4 -11 2 2
2 4 2
3 4 4
^^
^ ^
^ ^
B.rhead
A
B
pre
pb
h[1] h[3] h[4]h[2]
if(pa->j>pb->j)
{ 复制 pb 所指的结点赋值为 p ; if(pre==NULL) A.rhead[p->i]=p;
else pre->right=p;
p->right=pa;pre=p;
}
pb=pb->right;
pa
A.chead
A.rhead
1 1 3 1 4 5
2 2 -1
3 1 2
^
^^
^ ^
^ ^
2 2 1
1 4 -11 2 2
2 4 2
3 4 4
^^
^ ^
^ ^
B.rhead
B.chead
M
N
pa
pb
1 2 2prehl[1] hl[3] hl[4]hl[2]
if(A.chead[p->j]==NULL && hl[p->j]==A.chead[p->j])
{A.chead[p->j]=p;p->down=hl[p->j];}
Else{ p->down=hl[p->j]->down;
hl[p->j]->down=p;
}
hl[p->j]=p;}
p
A.chead
A.rhead
1 1 3 1 4 5
2 2 -1
3 1 2
^
^^
^ ^
^ ^
2 2 1
1 4 -11 2 2
2 4 2
3 4 4
^^
^ ^
^ ^
B.rhead
B.chead
A
B
pa
pb
1 2 2prehl[1] hl[3] hl[4]hl[2]
A.chead
A.rhead
1 1 3 1 4 5
2 2 -1
3 1 2
^
^^
^ ^
^ ^
2 2 1
1 4 -11 2 2
2 4 2
3 4 4
^^
^ ^
^ ^
B.rhead
B.chead
A
B
pa
pb
1 2 2prehl[1] hl[3] hl[4]hl[2]
If(Pa->j=pb->j) {pa->e+=pa->e;
if(e!=0){pre=pa;
pa=pa- >right;pb=pb->right;}
else 删除 A 中该结点
^
A.chead
A.rhead
1 1 3 1 4 4
2 2 -1
3 1 2
^
^^
^ ^
^ ^
2 2 1
1 4 -11 2 2
2 4 2
3 4 4
^^
^ ^
^ ^
B.rhead
B.chead
A
B
Pa=NULL1 2 2hl[1] hl[3] hl[4]hl[2]
pre
Pb=NULL
A.chead
A.rhead
1 1 3 1 4 4
2 2 -1
3 1 2
^
^^
^ ^
^ ^
2 2 1
1 4 -11 2 2
2 4 2
3 4 4
^^
^ ^
^ ^
B.rhead
B.chead
A
B
1 2 2hl[1] hl[3] hl[4]hl[2]
pa
pb
A.chead
A.rhead
1 1 3 1 4 4
2 2 -1
3 1 2
^
^^
^ ^
^ ^
2 2 1
1 4 -11 2 2
2 4 2
3 4 4
^^
^ ^
^ ^
B.rhead
B.chead
A
B
1 2 2hl[1] hl[3] hl[4]hl[2]
pa
pb
A.chead
A.rhead
1 1 3 1 4 4
2 2 -1
3 1 2
^
^^
^ ^
^ ^
2 2 1
1 4 -11 2 2
2 4 2
3 4 4
^^
^ ^
^ ^
B.rhead
B.chead
A
B
1 2 2hl[1] hl[3] hl[4]hl[2]
p
pb
Pa=NULL^
A.chead
A.rhead
1 1 3 1 4 4
2 2 -1
3 1 2
^
^^
^ ^
^ ^
2 2 1
1 4 -11 2 2
2 4 2
3 4 4
^^
^ ^
^ ^
B.rhead
B.chead
A
B
1 2 2hl[1] hl[3] hl[4]hl[2]
p
pb
Pa=NULL^
^
A.chead
A.rhead
1 1 3 1 4 4
3 1 2
^
^^
^ ^
2 2 1
1 4 -11 2 2
2 4 2
3 4 4
^^
^ ^
^ ^
B.rhead
B.chead
A
B
1 2 2hl[1] hl[3] hl[4]hl[2]
pb
Pa=NULL^
5.4 广义表的定义
广义表是线性表的推广,也称为列表( lists )广义表中元素既可以是原子类型,也可以是列表。记为: LS = ( a1 , a2 , …… , an )
广义表名 a1 是表头 (Head ) ( a2 ,…,an )是表尾 (Tail)
1 、定义:
n 是表长
① 第一个元素是表头,而其余元素组成的表称为表尾; 所以任何一个非空表,表头可能是原子,也可能是 列表;但表尾一定是列表。② 约定:用小写字母表示原子类型,用大写字母表示 列表。
在广义表中约定:
2 、特点:
1 )次序性:一个直接前驱和一个直接后继
2 )长度:表中最外层包含元素个数
3 )深度:当广义表全部用原子代替后,表中括号的最大重数
空表( )的深度为 1 ,长度为 0 ,原子的深度为 0.
4 )可递归:自己可以作为自己的子表。
例 E=(a, E) 递归表的深度是无穷值,长度是 2 。
5 )可共享 : 可以为其它广义表所共享的表。
6)任何一个非空广义表 LS = ( 1, 2, …, n)
均可分解为 表头 GetHead(LS) = 1 和 表尾 GetTail(LS) = ( 2,
…, n) 两部分
E=(a,E)=(a,(a,E))= (a,(a,(a,…….))) , E 为递归表
1 ) A =( )
2 ) B = ( e )
3 ) C =( a ,( b , c , d ) )
4 ) D=( A , B ,C )
5 ) E=(a, E)
例 1 :求下列广义表的长度。n=0 ,因为 A 是空表
n=1 ,表中元素 e 是原子
n=2 , a 为原子, (b,c,d) 为子表
n=3 , 3 个元素都是子表
n=2 , a 为原子, E 为子表D=(A,B,C)=(( ),(e),(a,(b,c,d))) ,共享表
6)F=(( )) n=1
Gethead(B)= GetTail(B)=
GetHead(C)= GetTail(C)=
GetHead(D)= GetTail(D)=
GetHead(E)= GetTail(E)=
GetHead(( ))= GetTail(( ))=
B = ( e ) C =( a ,( b , c , d ) ) D=( A , B ,C ) E=(a, E)
e ( )
a ((b,c,d))
A (B,C)
a (E)
例 2 :求下列广义表的表头和表尾。
GetTail( GetHead ((a,b),(c,d))) =( b)
( ) ( )
D
A B C
e
a
b c d
② A=( a , (b, A) )
例 3 :试用图形表示下列广义表 .(设 代表原子, 代表子表) e
① D=(A,B,C)=( ( ),(e),( a, (b,c,d) ) )
A
a
b
①的长度为 3 ,深度为 3 ②的长度为 2 ,深度为∞∞深度=括号的重数= 结点的最大层数
广义表的抽象数据类型定义
ADT GList{
数据对象: D={ ei|i=1 , 2 …, , n ; n≥0 ; ei
AtomSet 或 ei Glist, AtomSet 为某个数据对象 }
数据关系: R1={<ei-l , ei>| ei-l , ei D , 2≤i≤n}
基本操作: InitGList(&L)
操作结果:创建空的广义表 L
CreateGList(&L , S)
初始条件: s 是广义表的书写形式串 操作结果:由 s创建广义表 L
DestroyGList(&L)
初始条件:广义表 L 存在。 操作结果:销毁广义表 L
COpyGList(&T , L)
初始条件:广义表 L 存在。 操作结果:由广义表 L 复制得到广义表 T
GListLength(L)
初始条件:广义表 L 存在。 操作结果:求广义表 L 的长度,即元素个数。GListDepth(L)
初始条件:广义表 L 存在。 操作结果:求广义表 L 的深度。
GListEmpty(L)
初始条件:广义表 L 存在。 操作结果:判定广义表 L 是否为空。GetHead(L)
初始条件:广义表 L 存在。 操作结果:取广义表 L 的头。GetTail(L)
初始条件:广义表 L 存在。 操作结果:取广义表 L 的尾。
InsertFirst_GL(&l , e)
初始条件:广义表 L 存在。 操作结果;插入元素 e 作为广义表 L 的第一元素。DeleteFirst_GL(&L , &e)
初始条件:广义表 L 存在。 操作结果:删除广义表 L 的第一元素,并用 e返回其值Traverse_GL(L , Visit())
初始条件:广义表 L 存在。 操作结果;遍历广义标 L, 用函数 visit 处理每个元素。 }ADT GList
5.5 广义表的存储结构
由于广义表的元素可以是不同结构(原子或列表),难以用顺序存储结构表示,通常用链式结构,每个元素用一个结点表示。
1.1. 原子结点原子结点2.2. 表结点表结点
注意:列表的“元素”还可以是列表,所以结点可能有两种形式
方法 1 :表头、表尾表示法
表结点 :
原子结点:
5.5 广义表的存储结构
value tag=0
标志域 数值域
tphptag=1
标志域 表头指针 表尾指针
① A =( )
^1
0 e
③ C =( a ,( b , c , d ) )
1 ^1
10 a
0 b 0 d0 c
1 ^1
例:
② B=( e )
A=NULL ^^1
5.5 广义表的存储结构
⑤ E=(a, E)
④ D=(A,B,C)= (( ),(e),(a,(b,c,d)))
0 a
^1
1
^1
0 e
1 ^1
1 ^1
10 a
0 b 0 d0 c
1 ^1
^1
5.5 广义表的存储结构
tp atomtag=0
标志域 值域 表尾指针
方法 2:子表表示法
指向下一结点
tphptag=1
标志域 表头指针 表尾指针
表结点 :
原子结点:
5.5 广义表的存储结构
① A =( )
^1
③ C =( a ,( b , c , d ) , e)
^1
b0
0 a
c0 ^d0
例:
② B=( e )
A=NULL ^^1
^e0
1
B
C
^e0
本章小结理解数组的逻辑结构是线性结构的推广;掌握数组在行优先存储和列优先存储结构中的地址计算
方法 (注意我们所讲的公式前提是在数组各下界为 0)掌握对特殊矩阵进行压缩存储时的下标变换公式;理解稀疏矩阵的三种压缩存储方法的特点和适用范围,
以三元组表示稀疏矩阵时进行矩阵转置所采用的处理方法;掌握广义表的定义、存储结构;
书面作业 :
p32: 5.8 题 ,
p33: 5.10 题, 5.12 题p35: 5.25 题上机作业 :
识别广义表的表头和表尾 (p138)
作 业