串
串是一种特殊结构的线性表。区别在于线性表中用ElemType存储元素,串中用char存储字符。
串的存储结构
顺序存储
//1、定长的顺序串
#define MAXLEN 255 //预定义最大串长为255
typedef struct{
char ch[]; //每个分量存储一个字符
int length; //串的实际长度
}SString;
//用静态数组实现固定长度的顺序存储串
//分配连续的存储空间,每个char字符占1B,另外需要4个字节的length
//2、动态数组实现的顺序串——区别于链式存储
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;
//存储密度低,每个字符1B,每个指针4B
//改进如下:
typedef struct StringNode{
char ch[4];
struct StringNode* next;
}StringNode,* String;
字符串模式匹配算法
字符串模式匹配:在主串中找到与模式串相同的字串,并返回其所在位置。
408中需要掌握的两种模式匹配算法:朴素模式匹配算法、KMP算法。
朴素模式匹配算法
设主串长度为n,模式串长度为m。将主串中所有长度为m的子串依次与模式串对比,直到找到一个完全匹配的子串,或所有的子串都不匹配为止。最多对比n-m+1个子串(比如主串15,模式串12,那么主串比子串长了2,在加上12自己本身,就是2+1=3个,因此最后还要再加1)。
// Index(S,T):定位操作。若主串S中存在与串T值相同的子串,则返回它在主串S中第一次出现的位置;否则函数值为0。采用的就是模式匹配算法
int Index(SString S,SString T){
int i = 1;
int n = StrLength(S);
int m = StrLength(T);
SString sub; //用于暂存子串
while(i<=n-m+1){ //最多对比n-m+1个子串
SubString(sub,S,i.m); //取出从位置i开始,长度为m的子串
if(StrCompare(sub,T)!=0) ++i; //如果不匹配,则匹配下一个子串
else return i; //返回子串在主串中的位置
}
return 0; //S中不存在与T相等的子串
}
//该算法已经实现了模式串匹配算法,但是使用了SubString和StrCompare两个操作
//接下来不使用字符串的基本操作,直接通过数组下标实现朴素匹配模式算法
指针 i 指向主串 S ,指针 j 指向模式串 T ,若当前子串匹配失败,则主串指针 i 指向下一个子串的第一个位置,模式串指针 j 回到模式串的第一个位置
i = i - j + 2; //主串指针 i 指向下一个子串的第一个位置
j = 1; //模式串指针 j 回到模式串的第一个位置
若 j > T.length,则当前子串匹配成功,返回当前子串的第一个字符的位置 —— i - T.length
//朴素模式匹配算法
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;
}
//设设主串长度为n,模式串长度为m,最坏时间复杂度为O(mn)
朴素模式匹配算法思想总结:
1、主串长度为n,模式串长度为m
2、将主串中所有长度为m的子串依次与模式串对比
3、找到第一个与模式串匹配的子串,并返回子串起始位置
4、若所有子串都不匹配,则返回0
KMP算法
1、根据模式串 T,求出 next 数组
2、利用next数组进行匹配(主串指针不回溯)
手算求模式串的next数组
next数组的作用:当模式串的第 j 个字符匹配失败时,从模式串的第 next[j] 的继续往后匹配。
1、任何模式串都一样,第一个字符不匹配时,只能匹配下一个子串,因此,往后余生,next[1]都无脑写0。
2、任何模式串都一样,第二个字符不匹配时,应尝试匹配模式串的第一个字符,因此,往后余生,next[2]都无脑写1。
3、在后面的字符发生不匹配时,在不匹配的位置前面划一条分界线,模式串一步一步往后退,直到分界线之前“能对上”,或模式串完全跨过分界线为止。
获得next数组之后,就可以使用next数组进行模式匹配了。
KMP算法的进一步优化
优化之后的KMP算法逻辑不变。只是对next数组的优化,将next数组优化为了nextval数组。
先求next数组,再由next数组求nextval数组。
求nextval数组的步骤如下:
nextval[1] = 0; //nextval[1]无脑写0
for(int j = 2; j <=T.length; j++){ //从第二个字符开始往后
if(T.ch[next[j]] == T.ch[j])
//如果第j个字符和根据next数组要跳转至的字符相同,则让nextval中j的值与next[j]的值相同
nextval[j] = nextval[next[j]];
else
//如果不相同的话,那么nextval的j和next的j相同
nextval[j] = next[j];
}
// T为模式串,j为模式串字符的序号
附上一张练习: