这是我参与「掘金日新计划 · 2 月更文挑战」的第 二十六 天,点击查看活动详情
第四章__串、数组和广义表
4.1、串的定义
串(String):零个或多个任意字符组成的有限序列
1、关于串的术语
-
子串:一个串中任意个连续字符组成的子序列(含空串)称为该串的子串
- 真子串: 不包含自身的所有子串
-
主串:包含子串的串相应的称为主串
-
字符位置: 字符在序列中的序号为该字符在串中的位置
-
子串位置:子串第一个字符在主串中的位置
-
串相等: 当且仅当两个串的长度相等并且各对应位置上的字符都相同时,这两个串才相等
4.2、串的类型定义、存储结构及其运算
4.2.1、串的抽象类型定义
串的抽象数据类型定义如下:
ADT String {
数据对象: D = { ai | ai 属于 CharatcterSet, i = 1, 2, ... ,n, n>=0}
数据关系: R1 = {<ai-1, ai> | ai - 1, ai 属于 D, i = 2, ..., n}
基本操作:
StrAssign(&T chars);
初始条件: chars时字符串常量
操作结果: 生成一个其值等于chars的串T
StrCopy(&T, S);
初始条件: 串S存在。
操作结果: 由串S复制得串T
StrEmpty(S);
初始条件: 串S存在。
操作结果: 若S为空串,则返回true,否则返回false
StrCompare(S, T);
初始条件: 串S和T存在
操作结果: 若S > T 则返回值 > 0, 若S = T,则返回值=0, 若S < T, 则返回值 < 0
StrLength(S);
初始条件: 串S存在。
操作结果: 返回S的元素个数,称为串的长度
ClearString(&S);
初始条件: 串S存在。
操作结果: 将S清为空串。
Concat(&T, S1, S2);
初始条件: 串S1和S2都存在
操作结构: 用T返回由S1和S2联接而成的新串
Index(S, T, pos);
初始条件: 串S和串T存在,T是非空串, 1 <= pos <= StrLength(S)
操作结果: 若主串中存在和串T值相同的子串,则返回它在主串S中第pos个字符之后第一次出现的位置,否则函数值为0
Replace(&S, T, V);
初始条件: 串S, T和V存在,T是非空串
操作结果: 用V替换主串S中出现的所有与T相等的不重叠的子串
StrInsert(&S, pos, T);
初始条件: 串S和T存在, 1 <= pos <= StrLength(S) + 1
操作结果: 在串S的第pos个字符之间插入串T
StrDelete(&S, pos, len);
初始条件: 串S存在,1 <= pos <= StrLength(S) - len + 1
操作结果: 从串S中删除第pos个字符起长度为len的子串
DestroyString(&S);
初始条件: 串S存在
操作结果: 串S被销毁
}ADT String
4.2.2、串的存储结构
串也有两种存储结构:顺序存储和链式存储,考虑到存储效率和算法的方便性,串多采用顺序存储。
1、串的顺序存储结构
串的顺序存储结构定义:
#define MAXLEN 255
typedef struct {
char ch[MAXLEN + 1]; // 存储串的一位数组
int length; // 串当前的长度
}SString;
2、串的链式存储结构——块链结构
#define CHUNSIZE 80 // 块的大小可用户定义
typedef struct Chunk {
char ch[CHUNSIZE];
struct Chunk *next;
}Chunk;
typedef struct {
Chunk *head, *tail; // 串的头指针和和尾指针
int curlen; // 串的当前长度
}LString;
4.2.3、串的模式匹配算法
- BF算法(Brute-Force,又称古典的、经典的、朴素的、穷举的)
- KMP算法(特点:速度快)
BF算法
- Brute-Force简称为BF算法,也称简单匹配算法。
- 思路:从S的第一个字符开始依次与T的字符进行匹配
【算法步骤】
-
分别利用计数指针 i 和 j 指示主串S和模式T中当前正待比较的字符位置,i 初值为pos,j 初值为1。
-
如果两个串均为比较到串尾,即 i 和 j 均分别小于等于S 和 T的长度时,则循环执行以下操作
- S.ch[i] 和 T.ch[j] 比较,若相等,则 i 和 j 分别指示串中下个位置,继续比较后续字符;
- 若不相等,指针后退重新开始匹配,从主串的下一个字符( i = i + 2 )起再重新和模式的第一个字符( j = 1)比较
- 如果 j > T.length, 说明模式T中的每个字符依次和主串S中的一个连续的字符序列相等,则匹配成功,返回的模式T中第一个字符相等的字符在主串S中的序号( i - T.length)否则称匹配不成功,返回0。
【算法描述】
int Index_BF(SSTring S, SString T, int pos) {
// 返回模式T在主串S中第pos个字符开始第一次出现的位置。若不存在,则返回值为0
// 其中, T非空,1 <= pos <= S.length
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; // 匹配失败
}
【算法过程演示】
下图展示模式T = "abcac" 和主串S的匹配过程(pos = 1)
4.3、数组
- 数组:按一定格式排列起来的,具有相同类型的数据元素的集合。
- 一维数组:若线性表中的数据元素为非结构的简单元素,则称为一维数组
- 一维数组的逻辑结构:线性结构。定长的线性表。
- 声明格式:数据类型 变量名称[长度];
- 二维数组:若一维数组中的元素又是一维数组结构,则称为二维数组
- 二维数组的逻辑结构:非线性结构、线性结构
- 声明格式: 数据类型 变量名称[行数] [列数];
4.3.1、数组的抽象数据类型定义:
4.3.2、数组的顺序存储结构
- 数组特点:结构固定——维数和维界不变
- 数组基本操作:初始化、销毁、取元素、修改元素值
一维数组
LOC(i) = LOC(i-1)+l = a + i * l, i > 0
其中:l 是每个元素所占的存储单元,i 是元素的位置
例:有数组定义:int a[5] 每个元素占用4个字节,假设a[0]存储在2000单元,a[3]地址是多少?
LOC(0) = a = 2000 L = 4
LOC(3) = a + 3 * 4 = 2012
LOC(i) = a + i * L
二维数组
设数组开始存放位置 LOC( 0, 0 ) = a ,LOC ( j, k ) = a + j * m + k
以行序为主序:
设数组开始存储位置LOC(0, 0),存储每个元素需要L个存储单元
数组元素a[i] [j]的存储位置是:LOC(i, j) = LOC(0, 0) + (n * i + j) * L
例题:
三维数组
a[m1] [m2] [m3] 各维元素个数为m1, m2, m3
下标为i1, i2, i3 的元素的存储位置:
LOC(i_1,i_2, i_3) = a + i_1 * m_2 * m_3 + i_2 * m_3 + i_3
数组特点:结构固定——维数和维界不变
数组基本操作:初始化、销毁、取元素、修改元素值。
4.3.3、特殊矩阵的压缩存储
矩阵: 一个由m * n 个元素拍成的m行n列的表
矩阵的常规存储: 将矩阵描述为一个二维数组。
矩阵常规存储的特点:
- 可以对其元素进行随机存取
- 矩阵元素非常简单;存储的密度为1
不适合常规存储的矩阵: 值相同的元素很多且呈现某种规律分布;零元素多。
矩阵的压缩存储: 为多个相同的非零元素只分配一个存储空间;对零元素不分配空间。
1、对称矩阵
【特点】
在 n × n的矩阵a中,满足如下性质:aij = aji, (1 <= i, j <= n)
【存储方法】
只存储下(或者上)三角(包括主对角线)的数据元素。共占用n(n + 1) / 2个元素空间
【对称矩阵的存储结构】
对称矩阵上下三角中的元素数均为:n(n + 1) / 2,可以以行序为主序将元素存放在一个一维数组sa[n(n + 1) / 2]中
求aij的在一维数组中的位置:aij = a_k = i (i - 1) / 2 + (j - 1)
2、三角矩阵
【特点】
对角线以下(或者以上)的数据元素(不包括对角线)全部为常数c。
【存储方法】
重复元素c共享一个元素存储空间,共占用n(n + 1) / 2 + 1个元素
3、对角矩阵的存储(带状矩阵)
【特点】
在n * n的在方阵中,所有非零元素都集中在以对角线为中心的带状区域中,区域外的值全为0,则称为对角矩阵。
【存储方法】
4、稀疏矩阵存储
稀疏矩阵: 设在m * n 的矩阵中有t个非零元素。令 a = t / (m * n) 当a <= 0.05时称为稀疏矩阵
我们采用三元组的方式进行存储
例:试还原出下列三元组所表示的系数矩阵。
三元组顺序表又称:有序的双下标法
三元组顺序表的优缺点:
- 优点:非零元素在表中按行有序存储,因此便于进行以行顺序处理的矩阵运算
- 缺点:不能随机存取。若按行号存取某一行的非零元,则需从头开始进行查找。
4.4、广义表
定义: 广义表(又称列表Lists)是n >= 0个元素a_0, a_1, ... ,a(n-1)的有限序列,其中每一个ai或者原子,或者一个广义表。
广义表: LS=(a_1, a_2, ..., a_n),其中LS为表名,n为表的长度,每个ai为表的元素。
表头: 若LS非空(n >= 1),则其第一个元素a1就是表头。记作head(LS) = a1 注意:表头可以是原子,也可以是子表。
表尾: 除表头之外的其他元素组成的表。记作 tail(LS) = (a_2, ..., a_n) 注意:表尾不是最后一个元素,而是一个子表。
例题:
广义表的性质
- 有次序性:一个直接前驱和一个直接后继
- 有长度:=表中元素个数。例如: C= (a, (b, c))
- 有深度:=表中括号的重数 原子的深度为0, 空表的深度为1
- 可递归:自己可以作为自己的子表
- 可共享:可以为其他广义表所共享
- 多层次结构:广义表的元素可以是单元素,也可以是子表
广义表的基本运算
- 求表头GetHead(L):非空广义表的第一个元素,可以是一个单元素,也可以是一个子表
- 求表尾GetTail(L):非空广义表除去表头元素以外其它元素所构成的表。表尾一定是一个表