本章只需要掌握王道小题
一、基本概念
- 子串,主串
- 字符在串中的位置:字符在串中的序号,从1开始
- 子串在主串中的位置:以子串的第1个字符在主串中的位置来表示
- 空格串:由一个或多个字符组成,长度为空格的个数
- 空串:长度为0
二、基本操作
strAssign(&T,chars):赋值操作。把串T赋值为chars。
strCopy(&T,S):复制操作。由串s复制得到串T。
StrEmpty(S):判空操作。若s为空串,则返回 TRUE,否则返回 FALSE。
StrCompare(S,T):比较操作。若 S>T,则返回值>0;若 S=T,则返回值=0;若 S<T,则返回值<0。(ASCLL)
strLength(S):求串长。返回串s的元素个数。
Substring(&Sub,S,pos,len):求子串。用 sub 返回串s的第pos个字符起长度为len的子串。concat(&T,S1,S2):串联接。用T返回由 s1 和 s2 联接而成的新串。
Index(S,T):定位操作。若主串S中存在与串T值相同的子串,则返回它在主串S中第一次出现的位置,否则函数值为0
ClearString(&S):清空操作。将s清为空串。
DestroyString(&S):销毁串。将串S销毁。
三、存储结构
(一)定长顺序存储(静态数组)
超过MAXLEN的串长会被截断
串长表示:①用int length ②在串值后面加一个不计入串长的结束符"\0",此时串长为隐含值
#define MAXLEN 255 typedef struct{
char ch[MAXLEN];//每个分量分配一个字符
int length;//串的实际长度
}SString;
//运行结束自动回收空间
//一般char[0]废弃不用,从1开始的优点是字符位序和数组下标相同
1.求子串
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;
}
2.比较两个串的大小
//若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;
}
3.定位操作 若主串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)//两串相同则返回0
++i;//若不匹配则i++
else return i;//匹配成功返回i
}
return 0;
}
(二)堆分配存储(动态数组)
typedef struct{
char *ch//按串长度分配存储区,ch指向串的基地址
int length;//串的长度
}HString;
HString S;
S.ch = (char*)malloc(MAXLEN * sizeof(char));//申请堆区空间
S.length=0;
//需要用free()回收空间
(三)块链存储
链表
typedef struct StringNode{
char ch; //每个结点存一个字符
struct StringNode *next;
}StringNode,*String;
//缺点:ch1B,next4B,存储密度低
//改进:存储密度提高
typedef struct StringNode{
char ch[4]; //每个结点存多个字符
struct StringNode *next;
}StringNode,*String;
//若最后一个字符不够4个填不满,可以把#放进去
四、串的模式匹配
在主串中找到与模式串相同的子串,并返回其所在位置
此处采用定长顺序存储
Index已经实现了
(一)简单的模式匹配算法
暴力,不依赖于其他串操作
主串长n,模式串m,将主串中所有长度为m的子串(最多n-m+1个)依次与模式串对比
若n远远大于m,则最多进行n-m+1次匹配,每次最多进行m次比较,最坏时间复杂度O(mn)
int Index(SString S,SString T){
int i=1;j=1;//计数指针,表示主串S和模式串T中当前待比较的字符位置
while(i<=S[0]&&j<=T[0]){
if(S[i]==T[j]){
++i;++j; //继续比较后继字符
}
else{//当前子串匹配失败
i=i-j+2;j=1; //i指向下一个子串的第一个位置,j回到模式串的第一个位置
//i-(j-1)+1,j-1表示当前已匹配次数
}
}
if(j>T[0])//j所指的位置超出了模式串长度,匹配成功
return i-T[0];//返回当前子串第一个字符的位置
else
return 0; //没找到
}
(一)KMP算法(只考手算next数组选择题)
最坏时间复杂度O(m+n),其中,求next数组为O(m),模式匹配最坏为O(n)
根据模式串T,求出next数组,利用next数组进行匹配(主串指针不回溯,next反映模式串指针i的变化)
假设串的编号从1开始,若从0开始,则next数组需要整体-1
1.手算next数组
next[0]不适用,next[1]=0,next[2]=1是固定的
从next[3]开始,在不匹配的位置前边,划一根分界线,主串和线不动,模式串一步一步后退,直到分界线之前能匹配,或模式串完全在分界线右边。此时j指向哪里,next值就是多少
next[3]即在2和3之间画一条线,若模式串完全到了分界线右边,此时第三个字符匹配失败,则next[3]=1
2.KMP算法代码
//next[j]的含义是当模式串的第i个字符失配时,跳到next[j]位置继续比较
void get_next(char T[],int next[]){
i=1;
next[1]=0;
j=0;
while(i<=T[0]){ //T[0]用于保存字符串的长度
if(j==0||T[i]==T[j]){
++i;
++j;
next[i]=j;
}
else j=next[j];
}
}
int KMP(char S[],char T[],int next[],int pos) {
i=pos;
j=1;
while(i<=S[0]&&j<=T[0]){
if(j==0||S[i]==T[j]){//j=0时,下一步是两个都++
++i;
++j;
}
else//匹配失败时,主串指针不回溯,模式串指针改变
j=next[j];
}
if(j>T[0])
return i-T[0];
else
return 0;
}
3.KMP算法的优化
做next变化的前后为相同字母时,注定无法匹配,可以避免。若出现 Pj=Pnext[j],则需要再次递归,将next[j]修正为next[next[j]],直至两者不相等为止,更新后的数组命名为 nextval。计算 next 数组修正值的算法如下,此时匹配算法不变
(1)手算nextval数组
nextval[1]=0,其他的,若当前元素 不等于 当前元素的next[j]序号处的元素,则让nextval=next;若相等,则让nextval=nextval[next[j]]
(2)优化后的KMP
用nextval替代next
void get_nextval(SString T,int nextval[]){
int i=1,j=0;
nextval[1]=0;
while(i<r.length){
if(j==0||T.ch[i]==T.ch[j]){
++i; ++j;
if(T.ch[i]!=T.ch[i]) nextval[i]=j;
else nextval[i]=nextval[j];
}
else
i=nextval[i];
}
}