串、数组和广义表
串是内容受限的线性表:规定每个数据元素只能是字符
数组和广义表线性结构的推广——非线性结构
串 string
定义
零个或多个任意字符组成的有限序列
- S:串名
- a1a2……an:串值
- n:串长,n=0 时称为空串,用Φ表示
术语
- **字串:**一个串中任意个连续字符组成的子序列(含空串)
- **真字串:**不包含自身的所有子串
- **主串:**包含字串的串
- **字符位置:**字符在序列中的序号
- **子串位置:**子串第一个字符在主串中的位置
- **空格串:**由一个或多个空格组成的串
- **串相等:**两个串长度相等,且各个位置上对应的字符相等
**注:**所有空串都相等
应用
- 病毒感染检测
类型定义
ADT String{
数据对象:D = {ai | ai ∈ CharacterSet, i = 1,2,…,n, n≥0}
数据关系:R1 = {<ai-1,ai> | ai-1, ai ∈D, i=1,2,…,n}
基本操作:
StrAssign(&T,chars) // 串赋值
StrCompare(S) // 串比较
StrLength(S) // 求串长
Concat(&T,S1,S2) // 串连结
SubString(&Sub,S,pos,len) // 求子串
StrCopy(&T,S) // 串拷贝
StrEmpty(S) // 串判空
ClearString(&S) // 清空串
Index(S,T,pos) // 求子串位置
Replace(&S,T,V) // 串替换
StrInsert(&S,pos,T) // 子串插入
StrDelete(&S,pos,len) // 子串删除
DestroyString(&S) // 串销毁
}ADT String
串的顺序存储结构
// 较链式存储,顺序存储更多
#define MAXLEN 255
typedef struct{
char ch[MAXLEN+1]; // 存储串的一维数组
int length; // 串的当前长度
}SString;
串的链式存储结构
块链结构
#define CHUNKSIZE 80 // 定义块的大小
typedef struct Chunk{
char ch[CHUNKSIZE];
struct Chunk *next;
}Chunk;
typedef struct{
Chunk *head, *tail; // 串的头指针和尾指针
int curlen; // 串的当前长度
}LString; // 字符串的块链结构
串的模式匹配算法
**算法目的:**确定主串中所含子串(模式串)第一次出现的位置(定位)
**应用:**搜索引擎、拼写检查、语言翻译、数据压缩……
算法种类:
- BF算法(Brute-Force,古典的,经典的,穷举的,朴素的)
- KMP算法(速度快)
BF 算法
匹配失败后回溯:i = i-j+2;
,理解为:i = (i-j+1) + 1
算法思想: Index(S, T, pos)
- 将主串的第 pos 个字符和模式串的第一个字符比较,
- 若相等,继续逐个比较后续字符;
- 若不等,从主串的下一个字符开始,重新与模式串的第一个字符比较
- 直到主串的一个连续子串序列与模式串相等;返回值为 S 中与 T 匹配的子序列第一个字符的序号,即匹配成功;
- 否则,匹配失败,返回0
int Index_BF(SString S, SString T){
int i = 1, j = 1;
while(i<=S.length && j<=T.length){
if(S.ch[i] == T.ch[j]){++i; ++j;}
else{
i = i-j+2;
j = 1;
}
if(j >= T.length)
return i-T.length;
else
return 0;
}
}
// 添加查找位置 pos
int Index_BF(SString S, SString T, int pos){
int i = pos, j = 1;
while(i<=S.length && j<=T.length){
if(S.ch[i] == T.ch[j]){++i; ++j;}
else{
i = i-j+2;
j = 1;
}
if(j >= T.length)
return i-T.length;
else
return 0;
}
}
// 时间复杂度:若 m<<n,则为 O(n*m)
KMP 算法
KMP 算法是由 D.E.Knuth、J.H.Morris、V.R.Pratt 共同提出
这一算法较BF 算法,算法效率得到显著提高
算法思想:
- 利用已经部分匹配的结果,来加快模式串的滑动速度
- 主串 S 的指针 i 不必回溯,可提速到 O(n+m)
为此,定义 next[j]
函数,表明当模式中第 j 个字符与主串相应字符 ”失配“ 时,在模式串中需要重新和主串中该字符进行比较的字符的位置
// 求 next 数组
void Get_next(SString T, int &next[]){
i = 1; next[1] = 0; j = 0;
while(i<T.length){
if(j==0 || T.ch[i]==T.ch[j]){
++i; ++j;
next[i] = j;
}
else
j = next[j];
}
}
// KMP 算法
int Index_KMP(SString S, SString T, int pos){
int i = pos, j = 1;
while(i<=S.length && j<=T.length){
if(j==0 || S.ch[i] == T.ch[j]){++i; ++j;}
else
j = next[j]; // j 不变,i 后退
if(j > T.length)
return i-T.length;
else
return 0;
}
}
next 数组改进——nextval
void get_nextval(SString T, int &nextval[]){
i = 1; nextval[1] = 0; j = 0;
while(i<T.length){
if(j==0 || T.ch[i]==T.ch[j]){
++i; ++j;
if(T.ch[i] != T.ch[j])
nextval[i] = j;
}
else j = nextval[j];
}
}
数组
按一定格式排列起来,具有相同类型的数据元素的集合
二维数组定义:
typedef elemtype array2[m][n];
等价于
typedef elemtype array1[n];
typedef array1 array2[m];
线性表结构是数组结构的一个特例,数组结构是线性表结构的扩展
抽象数据类型定义
较为复杂,以二维数组举例
基本操作
数组的顺序存储
很少采用链式存储结构,因为数组的特点是结构固定,维数和维界不变;一般很少做插入与删除操作
所以,一般采用顺序存储结构来表示数组
**注:**数组可以多维,但存储数据元素的内存单元地址是一维的;因此需要将多为关系映射到一维关系
二维数组有两种存储方式
- 以行序为主
- 以列序为主
二维数组A[m][n]
,以行序为主序时,数组中任意元素 a[i][j]
的存储位置为:
特殊矩阵的压缩存储
**矩阵常规存储:**将矩阵描述为二维数组,其特点为
- 可以对元素随机存取
- 运算简单
- 存储密度为1
对称矩阵
在 n*n 的矩阵 a 中,满足:延主对角线对称
存储方法:
- 只存储下(或者上)三角,包括对角线的数据元素
共占用 n*(n+1)/2 个元素空间;
- 以行序为主序将元素存放在一个一维数组中
- 任一元素 aij 的存储位置为
i*(i-1)/2 + j - 1
三角矩阵
对角线上\下的数据元素(不包括对角线)全部为常数c
存储方法:
重复元素 c 共享一个元素存储空间,共占用 n*(n+1)/2+1 个元素空间,具体存储位置的计算:
对角矩阵(带状矩阵)
所有非零元素集中在以主对角线为中心的带状区域,其他位置值为0
存储方法:
以对角线的顺序存储,存为二维数组
稀疏矩阵
在 m*n 的矩阵中,非零元素个数为 t,若:
当 δ≤0.05
时称该矩阵为稀疏矩阵
存储方法:
-
由三元组 (i, j, aij) 唯一确定矩阵的一个非零元
-
存储矩阵的行数、列数、非零元素总个数
三元组顺序表法
它又称 有序的双下标法
- **优点:**非零元在表中按行序有序存储,便于进行依行顺序处理的矩阵运算
- **缺点:**不能随机存取,若按行存取某一行中的非零元,需要从头查找
链式存储:十字链表法
- **优点:**灵活地插入运算产生的新非零元素;删除因运算产生的新的零元素
十字链表中,矩阵的每一个非零元素用一个结点表示,该节点除了 (row, col, value)
外,还有两个域:
- **right:**链接同一行中下一个非零元素
- **down:**链接同一列中下一个非零元素
广义表
线性表的推广
它是 n 个元素 a1,…,an 的有限序列,其中每一个 ai 或者是原子,或者是另一个广义表
-
广义表通常记作:LS = ( a1,…,an )
LS
表名n
表的长度- ai 表的元素
-
习惯用大写字母表示广义表,小写字母表示原子
-
**表头:**若 LS 非空,则它的第一个元素 ai 就是表头 记作 head(LS) = ai **注:**表头可以是元素,也可以是子表
-
**表尾:**除表头以外的其它元素组成的表 记作 tail(LS) =( a2,…,an ) **注:**表尾不是最后一个元素,而是一个子表
性质
- 广义表的数据元素有相对次序:一个直接前驱和一个直接后继
- 广义表的长度定义为最外层包含的元素个数 例: C=(a, (b, c)),它是长度为 2 的广义表
- 广义表的深度定义为该广义表展开后所包含的括号重数 A=(b, c) 深度为1 B=(A, d) 深度为2 ”原子“的深度为0;”空表”的深度为1
- 广义表可以为其它表共享,例如上面:广义表 B 共享表 A
- 广义表可以是一个递归的表,例:F=(a, F) 注:递归表的深度无穷,长度为有限值
- 广义表是多层次结构,广义表的元素可以是单元素,也可以是子表;而子表的元素还可以是子表……
基本运算
- 求表头
GetHead(L)
非空广义表的第一个元素,可以是一个单一元素,也可能是一个子表 - 求表尾
GetTail(L)
非空广义表表尾一定是一个表 是除去表头元素以外的其它元素所构成的表