目录
【实验时使用C++操作】
【C++程序代码请观看[数据结构实验集]】
【用自己习惯的方式来记忆各种算法,而不是背课本!!!】
4 串
4.1 串的定义 和 基本操作
4.1.1 串的定义
·字符串,0个或多个字符组成的有限序列
·术语:
子串:串中人一个连续的字符组成的子序列
主串:包含子串的串
字符在主串中的位置:字符在串中的位置
子串在主串中的位置:子串的第一个字符在主串中的位置
·串和线性表:
串是一种特殊的线性表,数据元素之间呈线性关系
串的数据对象限定为字符集(中文字符、英文字符、数字字符、标点字符)
串的基本操作(增删改查)通常以子串作为操作对象
4.1.2 串的基本操作
·赋值操作:StrAssign(&T,chars) 把串T赋值为chars
·复制操作:StrCopy(&T,S) 把串S复制到串T
·判空操作:StrEmpty(S) 若串S为空串,返回TRUE,否则返回FALSE
·求串长:StrLength(S) 返回串S的元素个数
·清空操作:ClearString(&S) 将串S清为空串
·销毁串 :DestroyString(&S) 将串S销毁(回收存储空间)
·串联接:Contact(&T,S1,S2) 用T返回由S1和S2联接而成的新串
·求子串:SubString(&Sub,S,pos,len) 用Sub返回串S的第pos个字符起长度为len的子串
·定位操作:Index(S,T) 若主串S中存在和T相同的子串,则返回他在主串S中第一次出现的位置;否则函数值为0
·比较操作:StrCimpare(S,T) 若S>T,返回值>0;若S=T,返回值=0;若S<T,返回值<0
4.1.3 字符集编码
·任何数据存储到计算机中一定是二进制数
·编码:确定一个字符和二进制数的对应规则
·字符集:英文字符--ASCII字符集,中英文--Unicode字符集
4.2 串的存储结构 和 基本操作的实现
【王道教材采用——静态数组】
4.2.1 串的顺序存储
·静态数组实现(定长顺序存储表示)
·动态数组实现(堆分配存储表示)
4.2.2 串的链式存储
·每个节点存1个字符(存储密度低:每个制度1B,每个指针4B)
·每个节点存多个字符(存储密度提高)
4.2.3 基本操作的实现
·求子串:
#define MAXLEN 255 // 预定义最大串长255
typedef struct{
char ch[MAXLEN]; // 每个分量存储一个字符
int length; // 串的实际长度
}SString;
// 求子串
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;
}
·比较操作:
//比较操作。 若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;
}
·定位操作(简单匹配):
【建议使用朴素模式匹配算法,其原理一致】
int Index(SString S, SString T){
int u=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; // S中不存在与T相等的子串
}
4.3 串的朴素模式匹配算法
模式串:尝试在珠串中找到的串,未必存在
串的模式匹配:在主串中找到与模式串相同的子串,并返回其所在位置。
4.3.1 简单的模式匹配算法
int Index(SString S, SString T){
int k=1;
int i=k; j=1;
while(i<=S.length && j<=T.length){
if(S.ch[i] == T.ch[j]){
i++; j++; // 继续比较后继字符
} else{
k++; i=k; j=1; // 检查下一个子串
}
}
if(j > T.length)
return k;
else
return 0;
}
4.3.2 朴素模式匹配算法性能分析
模式串长度为m,主串长度为n,则:
·匹配成功的最好时间复杂度:O(m)
·匹配失败的最好时间复杂度:O(n-m+1) = O(n-m) ≈ O(n)
·匹配成功/失败的最坏时间复杂度:O(nm)
4.4 KMP算法
朴素模式匹配算法的缺点:某些子串与模式串能部分匹配时,主串的扫描指针i经常回溯,导致时间开销增加。
4.4.1 字符串的前缀、后缀、部分匹配值
前缀:除最后一个字符以外,字符串的所有头部子串
后缀:除第一个字符字符外,字符串的所有尾部子串
部分匹配值:字符串的前缀和后缀的最长相等前后缀长度 → next数组
→ 当字符不匹配时,按照公式算出子串需要后移的位数:移动位数 = 已匹配的字符串 - 对应的部分匹配值
4.4.2 KMP算法的原理
KMP算法:当子串和模式串不匹配时,主串指针i不回溯,模式串指针j=next[j]
KMP算法平均时间复杂度:O(m+n)
next数组手算方法:当第j个字符匹配失败,由前1~j-1个字符组成的串记为S,则:
next[j]=S的最长相等前后缀长度+1;特别的,next[1]=0
KMP算法步骤:
(1)计算next数组
(2)根据主串S,模式串T,next数组 进行KMP匹配
// 求模式串T的next数组
void get_next(String T,int 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; // 若pi=pj,则 next[j+1]=next[j]+1
}
else
j = next[j]; // 令j=next[j],循环继续
}
}
// KMP算法
int Index_KMP(SString S, SString T){
int i=1, j=1;
int next[T.length+1];
get_next(T,next); // 求模式串的next数组
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数组修正值的算法:
void get_nextval(String T, int 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];
}
}
【参考资料】