4.1 串的数据类型定义
4.1.1 串的定义
串,即字符串(String),是由零个或多个字符组成的有限序列。 一般记为s = ‘a1a……a,’ (n ≥0) 其中,S是串名,单引号括起来的字符序列是串的值; a,可以是字母、数字或其他字符; 串中字符的个数n称为串的长度。 n = 0时的串称为空串(用0表示)。 子串:串中任意个连续的字符组成的子序列。 (空串也是字符串的子串) 主串:包含子串的字符串。 字符在主串中的位置:字符在串中的序号 eg: E = ‘Helloworld’,'w’在E中的位置是6. 子串在主串中的位置:子串的第一个字符在主串中的位置。
4.1.2 串和线性表的区别
串是一种特殊的线性表,数据元素之间呈线性关系。 串的数据对象限定为字符集(如中文字符、英文字符、数字字符、标点字符等)串的基本操作,如增删改查等通常以子串为操作对象。
4.1.3 基本操作
假设有串T="",S=“iPhone 11 Pro Max?”,W=“Pro”
StrAssign(&T,chars):赋值操作。把串T赋值为chars。
StrCopy(&T,S):复制操作。由串s复制得到串T。
StrEmpty(S):判空操作。若s为空串,则返回TRUE,否则返回FALSE
StrLength(S):求串长。返回串s的元素个数。
ClearString(&S):清空操作。将s清为空串。
DestroyString(&S):销毁串。将串s销毁(回收存储空间)
Concat(&T,S1,S2):串联接。用T返回由S1和S2联接而成的新串
SubString(&Sub,S,pos,len):求子串。用sub返回串s的第pos个字符起长度为len的子串。
Index(S,T):定位操作。若主串S中存在与串T值相同的子串,则返回它在主串S中第一次出现的位置;否则函数值为0。
StrCompare(S,T):比较操作。若S>T,则返回值>0;若S=T,则返回值=0;若S<T,则返回值<0。
eg: 执行基本操作Concat(&T, S, W)后,T="iPhone 11 Pro Max?Pro” 执行基本操作SubString(&T ,S,4,6)后,T=“one 11” 执行基本操作Index(S, W)后,返回值为11
4.2 串的三种存储表示
4.2.1 定长顺序存储结构
类似于线性表的顺序存储结构,用一组地址连续的存储单元存储串值的字符序列。在串的定长顺序存储结构中,为每个串变量分配一个固定长度的存储区,即定长数组。
typedef struct {
char ch[MAXLEN];
int length;
) sstring;
串的实际长度只能小于等于MAXLEN,超过预定义长度的串值会被舍去,称为截断。在一些串的操作(如插入、联接等)中,若串值序列的长度超过上界MAXLEN,约定用“截断”法处理,要克服这种弊端,只能不限定串长的最大长度,即采用动态分配的方式。
4.2.2 堆分配存储结构
typedef struct{
char *Ch;
int length;
}HString; <-----动态数组实现(堆分配存储)
HString S;
S.ch = (char *) malloc (MAXLEN * sizeof(char));
S.length = 0;
利用malloc()为每个新产生的串分配一块实际串长所需的存储空间,若分配成功,则返回一个指向起始地址的指针,作为串的基地址,这个串由ch指针来指示;若分配失败,则返回NULL。已分配的空间可用free()释放掉。
4.2.3 块链存储结构
类似于线性表的链式存储结构,也可采用链表方式存储串值。由于串的特殊性(每个元素只有一个字符),在具体实现时,每个结点既可以存放一个字符,也可以存放多个字符。每个结点称为块,整个链表称为块链结构。 建议采用每个结点存放多个字符的链式存储结构
4.3 串的各种基本操作的实现及应用
4.3.1 求子串
SubString(&Sub,S,pos,len):求子串。
用Sub返回串S的第pos个字符起长度为len的子串。
bool SubString(SString &Sub,SString S, int pos,int len){
if (pos+len-1>S.length)
return false;
for (int i=pos;i<pos+len;i++)
Sub.ch[i-pos+1] = S.ch[i];
Sub.length = len;
return true;
}
4.3.2 比较两个串
StrCompare(S,T):比较操作。
若S>T,则返回值>0;若S=T,则返回值=0;若S<T,则返回值<0。
int StrCompare(SString S, SString T){
for (int i=1; i<=S.length && i<=T.length; i++){
if (S.ch[i]!=T.ch[i])
return S.ch[i]-T.ch[i];
}
return S.length-T.length;
}
4.3.3 定位操作
Index(S,T):定位操作。
若主串S中存在与串T值相同的子串,则返回它在主串S中第一次
出现的位置;否则函数值为0。
int Index(SString S, SString T){
int i=1, n=StrLength(S), m=StrLength(T);
SString sub;
while(i<=n-m+1){
SubString(sub,S,i,m);
if(StrCompare(sub, T)!=0) ++i;
else return i;
}
return 0;
}
4.4 串的模式匹配算法
字符串模式匹配:在主串中找到与模式串相同的子串,并返回其所在位置。
4.4.1 简单的模式匹配算法
主串长度为n,模式串长度为m; 朴素模式匹配算法:将主串中所有长度为m的子串依次与模式串对比,直到找到一个完全匹配的子串,或所有的子串都不匹配为止。 若当前子串匹配失败,则主串指针i指向下一个子串的第一个位置, 模式串指针j回到模式串的第一个位置。 最多对比n-m+1个子串 实现代码:
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;
}
最坏时间复杂度:O(mn) 最坏的情况,每个子串都要对比m个字符,共n-m+1个子串,复杂度= o((n-m+1)m) = O(nm)
最好时间复杂度:O(n) 第一个字符就一直都不匹配。
4.4.2 朴素模式匹配算法优化(KMP算法)
如下图:当模式串与主串的前五个字符都匹配时,只有第六个字符不匹配,这个字符之前的主串和模式串保持一致,所以就没有必要检查前面的字符。 由于前五个字符匹配,所以主串的前五个字符也是abaab,所以可令主串指针i不变,模式串指针j变为3再继续匹配即可,提高了算法效率。 这种结论对于模式串具有通用性,和主串内容没有关系。 可以用next数组来表示在不同位置匹配失败时需要修改的j的值。 这个操作主要的实现代码如下:
int Index_KMP(SString S,SString T,int next[]){
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;
}
4.4.3 KMP算法求next数组
next数组的作用:当模式串的第j个字符失配时,从模式串的第next[ij]的继续往后匹配。
例一: 任何模式串都一样,第一个字符不匹配时,只能匹配下一个子串,因此,next[1]永远都是0,next[2]永远都是1,next[3]也是1,next[4]也是1,next[5]是2,next[6]是1:
|