1
第五章 数组和广义表
数组和广义表的定义
数组和广义表的基本运算
数组的存储结构
广义表的存储结构
数
据
结
构
之
数
组
和
广
义
表
2
5. 1 数组的概念
?数组:是一种线性数据结构,它是有
限个相同类型数据元素的有序集合。
?数组的数据元素是值和下标的偶对,
对每个有定义的下标,都有一个相关
连的值。
a
11
a
12
… a
1n
A
mxn
= a
21
a
22
… a
2n
··
a
m1
a
m2
… a
mn
2
数
据
结
构
之
数
组
和
广
义
表
3
?基本操作:两种主要的运算:
( 1)给定一组下标,读取相应的数据
元素;
GetValue( A, &e, i, j )
( 2)修改元素值。
ChangeValue(A, e , i , j )
?数组一般不作插入和删除操作。
数
据
结
构
之
数
组
和
广
义
表
4
5. 2 数组的顺序表示和实现
? 逻辑多维到存储一维的映射,即寻找多维下标和
一维下标的关系。
? 存储方式
? 行序为主序的存储方式
? 以列序为主序的存储方式
a
11
:
a
1n
a
21
:
a
2n
:
a
mn
A
1
[1]
a
11
:
a
m1
a
12
:
a
m2
:
a
mn
A
1
[1]
以行为主
以列为主
3
数
据
结
构
之
数
组
和
广
义
表
5
? 数组元素的存储位置是其下标的线
性函数,存取数组中任一元素的时
间相等,所以,数组是随机的存储
结构。
数
据
结
构
之
数
组
和
广
义
表
6
? 计算一维下标
? 以二维数组 A[n][m]为例,计算数组元素
A[i][j] 的存储空间下标 w:
? 以行序为主序的存储方式
w=i*m+j
? 以列序为主序的存储方式
w= j*n+i
? 有三维数组 A[3][4][5],计算数组元素
A[2][3][3]的存储空间下标:以行序为主
w=2*(4*5)+3*5+3=58
4
数
据
结
构
之
数
组
和
广
义
表
7
? 数组的顺序存储表示和实现
typedef struct{
ElemType *base;
int dim ;
int *bounds ; //数组维界基址
int *constants ;//数组映象函数 (Page93的式 (5-2))
}Array; 常量 Ci基址, Cn=L=1,Ci-1 =bi*Ci;
第一维界 第二维界 第三维界 第四维界
bounds
2 (块 ) 5 (页 ) 3 (行 ) 4 (列 )
constants 5*12=60 3*4=12 4*1=4 1
c1=b2*c2 c2=b3*c3 c3=b4*c4 c4=1
数
据
结
构
之
数
组
和
广
义
表
8
? 数组的算法实现
#include <stdarg.h>
typedef void *va_list;
#define va_start(ap, parmN) (ap = ...)
#define va_arg(ap, type) (*((type *)(ap))++)
#define va_end(ap)
va_list ap;
va_start(ap,dim):使ap 指向dim后面的实参表;
va_arg(ap,int):取出ap所指向的int型数据后,
ap++。
va_end(ap):空语句
5
数
据
结
构
之
数
组
和
广
义
表
9
main( )
{ Array A ; int dim=4;
InitArray( A , dim , 2 , 5 , 3 , 4 );
……
}
数
据
结
构
之
数
组
和
广
义
表
10
Status InitArray(Array &A , int dim , ...){
if (dim<1 || dim>8 ) return ERROR ;
A.dim=dim;
A.bounds=(int*)malloc(dim*sizeof(int));
if( !A.bounds) exit(OVERFLOW);
elemtotal=1;
va_start(ap,dim);
for(i=0 ; i<dim ; i++){
A.bounds[i]=va_arg(ap , int );
if(A.bounds[i]<1) return UNDERFLOW;
elemtotal*=A.bounds[i]; }
va_end(ap) ;
6
数
据
结
构
之
数
组
和
广
义
表
11
A.base=(ElemType*)malloc(elemtotal*sizeof(ET));
if( ! A.base) exit(OVERFLOW);
A.constants=(int *)malloc(dim *sizeof(int));
if( !A.constants) exit(OVERFLOW);
A.constants[dim-1]=1;
for(i=dim-2 ; i >=0 ; i--)
A.constants[i]=A.constants[i+1]*A.bounds[i+1];
return OK; }
数
据
结
构
之
数
组
和
广
义
表
12
5. 3 特殊矩阵的压缩存储
?特殊矩阵:数值相同的元素或零元素
在矩阵中的分布有一定规律。
例:对称矩阵的压缩存储:以行序为主
序存储下三角中的元素,包括对角线
上的元素。
a
ij
= a
ji
1<= i, j <= n
7
数
据
结
构
之
数
组
和
广
义
表
13
(1) 二维下标为 ( i , j ),存储空间的一维下标
为 k,给出 k与 i , j 的关系( 1<= i, j <= n ,
0<= k < n*(n+1)/2);
k=(i-1)*i/2+j-1 ( i>=j) (i, j)在下三角区
k=(j-1)*j/2+i-1 (i< j) (i, j)在上三角区
int ij_TO_k(int i , int j){
if( i<1 || j<1 )return(-1);
if(i>=j) return((i-1)*i/2+j-1);
return ((j-1)*j/2+i-1); }
数
据
结
构
之
数
组
和
广
义
表
14
(2)输入并存储对称矩阵的算法 (以行序为主序 )
void Input(ET A[ ], int n){
for(k=0;k<(n+1)*n/2; k++)
scanf(&A[k]); }
(3)读取对称矩阵中某个元素的算法 :
Status GetElem(ET A[ ], int i, int j , ET &e) {
if(k=ij_TO_k(i , j)>=0){
e=A[k];return OK; }
else return ERROR; }
8
数
据
结
构
之
数
组
和
广
义
表
15
(4)修改对称矩阵中某元素的算法 :
Status Change(ET A[ ] ,int i, int j, ET e){
if(k=ij_TO_k(i , j)>=0) {
A[k]=e; return OK;
}
else return ERROR;
}
数
据
结
构
之
数
组
和
广
义
表
16
(5)输出打印对称矩阵的算法 :
void PrintPro( ET A[ ], int n){
for(i=1;i<=n;i++){
printf(“\n”);
for(j=1;j<=n;j++) {
GetElem(A, i, j, e);
printf(“%3d ”,e) ; }
}
}
9
数
据
结
构
之
数
组
和
广
义
表
17
思考题
上三角矩阵、下三角矩
阵、对角矩阵、等特殊矩
阵的压缩存储和相关的算
法描述。
数
据
结
构
之
数
组
和
广
义
表
18
?稀疏矩阵及其存储结构
? 定义:如果一个 m*n 矩阵的非零元素
个数 r比零元个数少,且非零元的分布
没有一定规律。
? 压缩存储:只存储稀疏矩阵的非零元素。
10
数
据
结
构
之
数
组
和
广
义
表
19
? 稀疏矩阵的三元组表存储方式
typedef struct { int i , j ; ElemType e ;
}Triple ;
Triple *data;
data[0]的三元分别用于存放矩阵总的行数、列
数和非零元个数;用三元组表示的非零元以行序
为主序依次存放在data中。
数
据
结
构
之
数
组
和
广
义
表
20
1 3 -3
1 6 15
2 1 12
2 5 18
3 1 9
3 4 24
4 6 -7
6 3 14
1 2 12
1 3 9
3 1 -3
3 6 14
4 3 24
5 2 18
6 1 15
6 4 -7
? 转置算法一
a i j v b i j v
由a求其转置矩阵b,需做以下事情:
⑴ 矩阵的行列值互换,即若a为 m×n,则 b为 n×m;
⑵ 每个三元组的行列号互换,即a
ij
对应于 b
ji
;
⑶ a中三元组以原矩阵的行号为序,而b中三元组是
以原矩阵的列号为序。
11
数
据
结
构
之
数
组
和
广
义
表
21
Statue TranSM( Triple A[ ] , Triple &B[ ]){
B[0].i=A[0].j; B[0].j=A[0].i; B[0].e=A[0].e ;
if( A[0].e){ k=1; //B 数组的下标
for(col=1;col<=A[0].j;col++)
for(p=1;p<=A[0].e;p++) //p是 A数组的下标
if(col==A[p].j){
B[k].i=A[p].j; B[k].j=A[p].i;
B[k].e=A[p].e ; k++;}
}
return OK ;
}
该方法实现直观、易理解,时间复杂度为 O(a.nu*a.tu)。
数
据
结
构
之
数
组
和
广
义
表
22
?快速转置
?算法思想
?设置num[l..a.nu]和cpot[l..a.nu]两个辅助向量;
?在a.data中求矩阵M的每一列中非零元素个数,存于辅
助向量num的对应列中;
?用递推公式算出每一列第一个非零元素在b.data中的
位置,存于辅助向量cpot的对应列中,递推公式为:
cpot[1]=1
cpot[col]=cpot[col-1]+num[col-1],
col=2..a.nu
?依次对a.data中的三元组进行转置,并按三元组的列
号,从cpot的对应列取出该三元组应存放在b.data中
的位置,然后将cpot该列中的值加1,使其记载该列下
一非零元素在b.data中的位置。
12
1 2 12
1 3 9
3 1 -3
3 6 14
4 3 24
5 2 18
6 1 15
6 4 -7
a i j v
1 2 3 4 5 6 7
num 2 2 2 1 0 1 0
cpot 1 3 5 7 8 8 9
用递推公式算出每一列第一个非零元素在
b.data中的位置,存于辅助向量cpot的对
应列中, cpot[1]=1
cpot[col]=cpot[col-1]+num[col-1],
col=2..a.nu
数
据
结
构
之
数
组
和
广
义
表
24
Status FastTranSM(Triple A[],Triple &B[]){
B[0].i=A[0].j; B[0].j=A[0].i ; B[0].e=A[0].e ;
if( A[0].e) { cpot[1]=1;
for(col=1;col<=A[0].j;col++) num[col]=0;
for(p=1;p<=A[0].e;p++) num[A[p].j]++;
for(col=2;col<=A[0].j;col++)
cpot[col]=cpot[col-1]+num[col-1];
for(p=1;p<=A[0].e;p++){
col =A[p].j; k =cpot[col]++;
B[k].i=A[p].j; B[k].j=A[p].i; B[k].e=A[p].e ; }
} return OK; }
该方法称为快速转置,时间复杂度为 O(a.nu+a.tu)。
13
数
据
结
构
之
数
组
和
广
义
表
25
? 稀疏矩阵的十字链表的存储方式
?参见 Page 103的叙述;
?结点包含的数据域有:非零元的三元组、行向后
继指针 right、列向后继指针 down。
typedef struct OLN{
int i , j ; ElemType e ;
struct OLN *right , *down ;
}OLNode;
链域down:链接同一列中下一个非零元;
链域right:链接同一行中下一个非零元;
稀疏矩阵中同一列的非零元通过down链接成
一个链表,同一行的非零元通过right链接
成一个链表。
i j e
down right
数
据
结
构
之
数
组
和
广
义
表
26
? 例:用十字链表表示下面的矩阵。
?
?
?
?
?
?
?
?
?
?
?=
0002
0010
5003
M
^^
1 1 3 1 4 5
2 2 -1
3 1 2
^
^^
M.chead
M.rhead
^^
14
数
据
结
构
之
数
组
和
广
义
表
27
十字链表的程序实现
Status CreatLinkmatrix(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 OVERFLOW; //内存分配失败
if(!M.chead=(Olink*)malloc((n+1)*sizeof(Olink))))
return OVERFLOW;
数
据
结
构
之
数
组
和
广
义
表
28
for( scanf(&i,&j,&e); i!=0;scanf(&i,&j,&e)){ //按任意次序输入
非零元
if(!(p=(OLNode*)malloc(sizeof(OLNode))))
return OVERFLOW;
p->i=i; p->j=j; p->e=e; //生成结点
if(M.rhead[i] ==NULL) 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; //完成行插入 }
15
数
据
结
构
之
数
组
和
广
义
表
29
if(M.chead[i] = =NULL) M.chead[j]=q; //产生第一个结点
else{
for(q=M.rhead[j]; (q->down) && q->down->i<i; q=q-
>down);
//寻找插入位置
p->down=q->down; q->down=p; //完成列插入
}
}
}
数
据
结
构
之
数
组
和
广
义
表
30
5. 4 广义表的定义
? 广义表:是 n(n>=0)个元素的集合。
LS=(d1 , d2 , d3 , ...... , dn )
? LS为 表名
? d
i
∈D
0
时,称d
i
为 单元素 (用小写字母表示);
? d
i
∈Lists时,称d
i
为 子表 (用大写字母表示);
? n为表的长度,当长度为0时称为 空表 ;
? n>0时,第一个元素d
1
为 表头 ;
? n>0时,(d
2
,…,d
n
)称为 表尾 。
16
数
据
结
构
之
数
组
和
广
义
表
31
?. 广义表的长度和深度
? 长度 n:是广义表的元素个数;
? 深度 k:
广义表的元素之间除了存在次
序关系外,还存在层次关系。 广义
表中元素的最大层次 为表的深度。
提示: 元素的层次就是包括该元
素的括号对的数目。
数
据
结
构
之
数
组
和
广
义
表
32
? 广义表举例:
? A= ( ) n= 0 , k =1
? B=(e) n=1 , k=1
? C=(a,(b,c,d)) n=2 , k =2
? D=( A , B , C ) n=3 , k = 3
{ 即D=((),(e),(a,(b,c,d))) }
? E=(a , E ) n=2 , k = ∞
{ 定义一个无限表(a,(a,(a, …))) }
Gethead(C )=a Gethead(D)=A
Gettail(C)=((b,c,d)) Gettail(B)=( )
17
数
据
结
构
之
数
组
和
广
义
表
33
5.5 广义表的链式存储结构
? 结点结构:
typedef struct GLN{
int tag;
union{ AtomType atom;
struct GLN *hp; };
struct GLN *tp;
}*Glist ;
广义链表表头表尾结点表示法 (Page 110)
tag=1 hp tp
tag=0 atom tp
原子结点
表结点
数
据
结
构
之
数
组
和
广
义
表
34
例: 画广义表的存储结构
C=(a,(b,c,(d))) ; n=2 ; k=3
C
1 ^
0 a 1 ^
0 b 0 c
0 d ^
广义表: F=(e,C,( )) , 长度: n=3; 深度: k=4
F
1 ^
0 e 1 1 ^ ^
1 ^
18
数
据
结
构
之
数
组
和
广
义
表
35
例:用存储结构表示广义表达式
A=( ) B=(e) C=(a, (b, c, d))
D=(A, B, C) =(( ), (e), (a, (b, c, c)))
E=(a. E)=(a, (a, (a, …)))
1 ^ ^A
CB
D
1 ^
1 ^
0 e ^
1 ^ 1 1 ^
E 1 ^
0 a 1 ^
1 ^
1 ^0 a
0 b 0 c 0 d ^
数
据
结
构
之
数
组
和
广
义
表
36
?本章重点
? 一般多维数组的存储及初始化
? 特殊矩阵的存储
? 稀疏矩阵的三元组表示和运算(初始
化和转置)
? 广义表的定义