串的定义和实现
串(
串中任意多个连续的字符组成的子序列称为该串的子串,包含子串的串称为主串。
某个字符在串中的序号称为该字符在串中的位置。子串在主串中的位置以子串的第一个字符在主串中的位置来表示。当两个串的长度相等且每个对应位置的字符都相等时,称这两个串是相等的。
串的逻辑结构和线性表极为相似,区别仅在于串的数据对象限定为字符集。串的基本操作:
:赋值操作。把串 赋值为 。 :比较操作。若 ,则返回值 ;若 ,则返回值 ;若 ,则返回值 。 :求串长。返回串 的元素个数。 :串联接。用 返回由 和 联接而成的新串。 :求子串。用 返回串 的第 个字符起长度为 的子串。 :复制操作。由串 复制得到串 。 :判空操作。若 为空串,则返回 ,否则返回 。 :定位操作。若主串 中存在与串 值相同的子串,则返回它在主串 中第一次出现的位置;否则函数值为 。 :清空操作。将 清为空串。 :销毁串。将串 销毁。
字符集编码:任何数据存到计算机中一定是二进制数。要确定一个字符和二进制数的对应规则,这就是“编码”。“字符集”:英文字符―—
串的存储结构有三种:
定长顺序存储表示(静态数组实现):类似于线性表的顺序存储结构,用一组地址连续的存储单元存储串值的字符序列。在串的定长顺序存储结构中,为每个串变量分配一个固定长度的存储区,即定长数组。
#define MAXLEN 255 // 预定义最大串长 typedef struct{ char ch[MAXLEN]; // 每个分量存储一个字符 int length; // 串的实际长度,只能小于等于 MAXLEN,超过部分被截断 }SString;
串长的表示方法:
- 用一个额外的变量
来存放串的长度。 - 在串值后面加一个不计入串长的结束标记字符"\0"(对应
码的 ),此时的串长为隐含值。 - 用
充当 ,优点:字符的位序和数组下标相同,但是字长最大为 。 废弃不用,用一个额外的变量 来存放串的长度。(教材使用的方法)
- 用一个额外的变量
堆分配存储表示(动态数组实现):以一组地址连续的存储单元存放串值的字符序列,但它们的存储空间是在程序执行过程中动态分配得到的。
typedef struct{ char *ch; // 按串长分配存储区,ch 指向串的基地址 int length; // 串的长度 }HString;
在
语言中,存在一个称之为“堆"的自由存储区,并用 和 函数来完成动态存储管理。利用 为每个新产生的串分配一块实际串长所需的存储空间,若分配成功,则返回一个指向起始地址的指针,作为串的基地址,这个串由 指针来指示;若分配失败,则返回 。已分配的空间可用 释放掉。 块链存储表示:类似于线性表的链式存储结构,也可采用链表方式存储串值。每个结点既可以存放一个字符,也可以存放多个字符。每个结点称为块,整个链表称为块链结构。最后一个结点占不满时通常用"#"补上。
typedef struct StringNode{ char ch[4]; // 每个结点存多个字符 struct StringNode *next; // 指向下一个结点 }StringNode, *String;
串的模式匹配
子串的定位操作通常称为串的模式匹配,它求的是子串在主串中的位置。这里采用定长顺序存储结构。(子串是主串的一部分,一定存在于主串,模式串不一定能在主串中找到)
朴素模式匹配算法:不依赖于其他串操作的暴力匹配算法。算法思想为:从主串
最坏时间复杂度为
int Index(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;
}
KMP算法:如果已匹配相等的前缀序列中有某个后缀正好是模式的前缀,那么就可以将模式向后滑动到与这些相等字符对齐的位置,主串
前缀指除最后一个字符以外,字符串的所有头部子串;
后缀指除第一个字符外,字符串的所有尾部子串;
部分匹配值则为字符串的前缀和后缀的最长相等前后缀的字符串长度。
以'
'
'的前缀和后缀都为空集,最长相等前后缀长度为 。 '
'的前缀为 ,后缀为 ,最长相等前后缀长度为 。 '
'的前缀为 ,后缀为 ,最长相等前后缀长度为 。 '
'的前缀为 ,后缀为 ,最长相等前后缀长度为 。 '
'的前缀为 ,后缀为 ,最长相等前后缀长度为 。 得到部分匹配值
表: 编号 将
表右移一位,得到 数组,第一个数用 填充:(为了算法方便,将 的值均加一) 编号 都写 , 都写 。 void get_next(SString T,int next[]){ // 求 next 数组 int i=1,j=0; next[1]=0; while(i<T.length){ if(j==0 || T.ch[i]==T.ch[j]){ ++i;++j; next[i]=j; }else{ j=next[j]; } } }
当
代码如下:
void get_nextval(SString T,int nextval[]){ // 求 next 数组优化后的 nextval 数组
int i=1,j=0;
nextval[1]=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 nextval[i]=nextval[j];
}else{
j=nextval[j];
}
}
}
算法步骤:
- 比较主串元素
和子串元素 是否相等。 - 若相等
均加一;若不等,将 的值修改为对应编号的 值(括号中加一后的值),若 的值为 ,则将 的值加一, 的值变为 。 - 若
的值大于子串的长度,则匹配成功。 - 否则继续步骤
,直到主串匹配到末尾或匹配成功。
int Index_KMP(SString S,SString T,int next[]){ // 字符串数组的第0位舍弃
int i=1,j=1;
while(i<=S.length && j<=T.length){
if(j==0 || S.ch[i]==T.ch[j]){
++i,++j; // 继续比较后继字符
}else{
j=next[j]; // 模式串向右移动
}
}
if(j>T.length)return i-T.length; // 匹配成功
else return 0;
}