数据结构 如线性表、栈和队列等,均是线性结构,其中的数据元素是非结构的原子类型。本章要讨论的数组,可以看成是线性表的扩展,即表中的元素本身也是一种数据结构。
本章简单讨论数组的逻辑结构及其存储方式。
数组的概念
【数组定义】
数组(Array)是由类型相同的数据元素构成的集合,每个数据元素称为一个数组元素(简称元素)。
一维数组是一个定长线性表。二维数组是一个定长线性表,它的每个元素是一个一维数组。
n维数组是线性表,它的每个元素是 n-1 维数组。
数组的特点:
(1)元素推广性:元素本身可以具有某种结构,而不限定是单个的数据元素。
(2)元素同一性:元素具有相同的数据类型。
(3)关系的确定性:每个元素均受 n (n>=1)个线性关系的约束,如二维数组的元素有2个下标,n维数组的元素有n个下标。元素个数和元素之间的关系一般不发生变动。
数组的存储结构
计算机的存储结构是一维的,而数组一般是多维的,怎样存放?
讨论:按照数据“存得进,取得出”的存储原则,需要找到数组下标与存储地址的对应关系。由于计算机的内存结构是一维线性的,因此存储多维数组时,需要解决将多维关系映射到一维关系的问题,即按某种次序将数组元素排成序列,然后将这个线性序列存放在存储器中。
数组结构特点:
数目固定
(1)数据元素数目固定,一旦定义了一个数组结构,就不再有元素个数的增减变化。
类型相同
(2)数据元素具有相同的类型。
关系固定
(3)数据元素的下标关系具有上下界的约束且下标有序。
数组基本运算:
(1)给定一组下标,存取相应的数据元素。
(2)给定一组下标,修改相应的数据元素中某个数据项的值。~~
1.多维数组
从数据结构的角度看,数组是 n(n>=0)个相同数据类型数据元素构成的有限序列,数组可以看成是一种特殊的线性表,是线性表的推广。
数组的特点决定了它适合于采用顺序存储结构。下面讨论二维数组元素在内存中的问题,即数组下标与内存地址的映射关系。
设二维数组每个数据元素占用了L个单元,m,n为数组的行数和列数,Loc(a11)表示元素的地址,行优先存储——基本思想是按行存储,即存完第 i 行再接着存储第 i+1 行,如下。
aij之前共有 ( (i-1)*n + j-1) 个元素
aij的存储地址 =Loc(aij ) = Loc(a11 )+ ( ( i-1)*n+ j -1 )*L
aij 之前共有 (( j-1)*m+ i-1 ) 个元素
aij 的存储地址 =Loc(aij ) = Loc(a11 )+ ( ( j-1)*m+ i -1 )*L
按上述两种方式顺序存储的数组,只要知道开始结点的存放地址(基地址),维数和每维的上、下界,以及每个数组元素所占用的单元数,就可以将数组元素的存放地址表示为其下标的线性函数。顺序存储的数组是一个随机存取结构。
数组元素求址
设数组a[1…60, 1…70]的基地址为2048,每个元素占2个存储单元,若以列序为主序顺序存储,求元素a[32,58]的存储地址。
注:a[1…60, 1…70]是数组的一种表示方法,表示行优先的二维数组,行下标范围为1到60,列下标范围为1到70。
解:
数组行数m=60-1+1=60; 列数n=70-1+1=70;
行下标i=32; 列下标j=58; 元素长度L=2;
列优先元素求址公式Loc(aij)=Loc(a11)+[(j-1)*m+(i-1)]*L
得:LOC(a32,58)=2048+[(58-1)*60+(32-1)]*2=8950
若数组是a[0…59, 0…69],结果是否仍为8950?
i、j均从0开始,求址公式为Loc(aij)=Loc(a00)+[j*m+i)]L
LOC(a32,58)=2048+[5860+32]*2=9072
2.矩阵的压缩存储
把相同的数据只存储一次,不但可以节省存储空间,还可以节约传输时间。
特殊矩阵:
值相同元素或者零元素分布有一定规律的矩阵
对称矩阵:上三角区域和下三角区域的值相同;
上三角矩阵:下三角部分的值都相同(图中的 c 表示同一个值,^符号表示省略的元素)。
矩阵压缩存储时会有什么问题?可以采用的方法是什么?
一是应该存什么样的数据及如何存储,二是保存的数据要能恢复原样。
问题一的分析与处理:
(1)相同的数据只存储一次;(2)零元素不占存储空间。
可以把要存储的数据放到一维数组中。
问题二的分析与处理:
找到同一元素在一维数组与矩阵中的对应关系即可恢复原有的矩阵形式。
矩阵压缩存储方法:
步骤1
确定存放压缩后数据的向量的空间大小;
步骤2
确定二维数组下标 i, j 与向量下标 k 的关系。
对称矩阵的压缩存储:
对称矩阵压缩存储方法:
用向量存储,对称矩阵元素可以只存储下或上三角部分
按“行优先顺序”存储主对角线(包括对角线)以下的元素,以一维数组 sa[M] 作为 n 阶对称矩阵 A 的存储结构,要找到 A 中任意一元素 aij 与它的存储位置 sa[k] 之间存在的对应关系。
共需要存储的元素数目M为:n(1+n)/2
k等于aij前的元素个数(此处k的值从0开始):
k =1+2+3+…+i+j = i(1+i)/2+j (i≥j)
三角矩阵的压缩存储
三角矩阵压缩存储方法:
除了存储主对角线及上(下)三角中的元素外,再加一个存储常数 c 的空间。
三角矩阵中的重复元素 c 可只用一个存储单元,其余的元素有 n(n+1)/2 个,因此,三角矩阵可压缩存储到长度为 n(n+1)/2 + 1 的向量中,其中 c存放在向量的最后位置,矩阵下标与向量下标间的关系如图:
对角矩阵的压缩存储
对角矩阵 :
所有非零元素都集中在以主对角线为中心的带状区域中的方阵,也称为带型矩阵。
对角矩阵压缩存储方法:
存储所有非零元素
三对角矩阵压缩方法一:
设有n阶三对角矩阵A[n][n],将三条对角线上的元素逐行存放于数组B[M]中,使得B[k]=A[i][j], 给出i、j与k的对应关系。
除第 1 行和第 n 行是 2 个元素外,每行的非零元素都是 3 个,因此,需存储的元素个数 M = 3n-2。
k=ai+bj+c (a、b、c分别为整数)
将i、j、k的取值带入方程,可求得a、b、c的值。
解得:a=2; b=1; c=0;
所以:k=2i+j —— (0<=i<n; 0<=j<n)
i=(k+1)/ 3 ——(0<=k<3n-2)
j=(k+1)/ 3+(k+1)% 3 -1
或者 j=k-2i=k-[(k+1)/3]*2 (注意除法是整除不可化简)
三对角矩阵压缩方法二 :
只存储带内的元素。
4.稀疏矩阵的压缩存储
稀疏矩阵:
设矩阵Amn中有s个非零元素,若s远远小于矩阵元素的总数(即s<<m×n),则称A为稀疏矩阵(sparse matrix)。
矩阵的稀疏因子:
令 e=s/(m*n),称e为矩阵的稀疏因子。
通常认为e≦0.05时称之为稀疏矩阵。
稀疏矩阵压缩存储原则:
存储所有非零元素 。
稀疏矩阵常用压缩存储形式:
1)三元组表
存储原则:三元组表存稀疏矩阵中各非零元的值、行列位置和矩阵的行列数。
【例】用三元组表的形式存储稀疏矩阵。
解:设稀疏矩阵为M[6,7],为方便管理,可以把矩阵的行、列、非零元素个数信息,专门用三元组的第0行来记录。
三元组的数据结构类型描述:
结点结构设计:
struct node
{ int row,col; // 非零元素的行下标和列下标
int value; // 非零元素值
};
typedef struct node NODE;
NODE matrix[MAX];
2)稀疏矩阵的链表结构
原因:在运算中若非0元素的位置发生变化,会引起数组元素的频繁移动,三元组表就不适合做稀疏矩阵的存储结构。
链式存储根据结点间链接方式的不同,可以有多种形式:
(1)带行指针向量的单链表表示法
每一个非零元素用一个结点表示,矩阵的每一行的非零元素用一个单链表存储。结点 struct node 成员 col 记录元素所在的列信息, value 记录元素值;所有链表的头地址信息汇集在数组 TAB[ROW] 中。
结点结构设计:
typedef struct node
{ int col , value ;
struct node *next;
} linklist;
linklist *TAB[ROW];
#define ROW 4
(2)十字链表的存储结构
在十字链表中,表示非0元素的结点除了三元组,还有两个指针域:向下域(down)链接同一列下一个非0元素,链接成一个带头结点的列循环链表;向右域(right)链接同一行下一个非0元素,链接成一个带头结点的行循环链表。
3.字符串
1)字符串的定义:
串是一种特殊的线性表,它是由n (≥ 0)个字符组成的有限序列
记作 s = “a1,a2, a3, … an”
s----串名, a1,a2, a3, … an----串值
ai是串中字符,n是串的长度
串的例子:
串的有关术语:
串的基本操作:
(1)字符串的长度计算
(2)字符串的复制
(3)字符串的连接
(4)字符串的替换
(5)字符串的插入
(6)字符串的删除
(7)字符串的比较
(8)抽取字符串
(9)字符串的分割
(10)字符串的查找
由于在许多高级语言中都提供相应的串操作处理功能,故对串的操作不再赘述。
2)字符串的存储结构
(1)串的顺序存储
串的顺序存储——方案1:用一个指针来指向最后一个字符。
数据结构设计:
typedef struct
{ char ch[MAXSIZE];
int curlen;
} SeqString;
串的顺序存储——方案2:直接记录串长
结构描述:char s[MAXSIZE+1];
串的顺序存储——方案3:在串尾存储一个不会在串中出现的特殊字符作为串的终结符
结构描述:char s[MAXSIZE+1];
串的顺序存储特点:
(1)预定义串的最大长度;
使得串的某些操作受限(截尾),如串的连接、插入、置换等运算;
(2)插入和删除操作不方便;
需要移动大量的字符
2)串的块链存储结构
单字符链串存储结构设计:
typedef struct linknode
{ char data;
struct linknode *next;
} linkstring;
每个链结点中只放一个字符,插入、删除、求长度等运算非常方便,但存储效率低。
改进的方法,可以在一个链结点中存储多个字符,这样就可以改善存储效率,在处理不定长的大字符串时很有效,这是顺序串和链串的综合折中,称为块链结构。
串的块链存储结构:
typedef struct linknode
{ char data[4];
struct linknode *next;
} linkstring;
3)串的索引存储方式
用串变量的名字作为关键字组织起来的索引表,表中串名与串值之间一一对应。
(1)带长度的串索引表
设:S1=“please”,S2= “seek”.
把每个串的首地址和长度记录下来,放在索引表——数据结构中,便于管理。
结构描述:
typedef struct
{
char name[maxsize];//串名
int length; //串长度
char *stadr; //串起始地址
} lnode
(2)带头尾指针的串索引表
设:S1=“abcdefg”, S2= “bcd”
一个串设置两个指针,一个指向串开头,一个指向串末尾
结构描述:
typedef struct
{
char name[maxsize];//串名
char *stadr, //串头地址
char *enadr; //串尾地址
} enode;
(3)带特征位的串索引表
设:S1=“abcdefg” S2= “bcd”
在索引表中设置一标志量tag来标示存储的是串地址还是串内容,这样设计可以让较短的串存取便捷一些。
结构描述:
typedef struct
{
char name[maxsize];
int tag; // 特征位
union //共用体变量
{
char *stadr; // 特征位为0,放串首地址
char value[4]; //特征位为1,放串值
} uval;
} tagnode
(4)链式串索引表
设:s1=“GOOD” s2= “DAY”
//串链结点定义
typedef struct linknode
{ char data;
struct linknode *next;
} linkstring;
// 串链数组定义
typedef struct
{ char name[maxsize]; //串名
linkstring *link;
} linkstru;
linkstru aStr [N];