前言
参考图片来自B站懒猫老师,老师讲得很细致。
放一下连接:懒猫老师B站视频目录集合
提示:以下是本篇文章正文内容
一、串的定义
串(string)是由零个或多个字符组成的有限序列
子串:串中任意个连续的字符组成的子序列
主串:包含子串的串
空串:0个字符的串
子串的位置:子串的第一个字符在主串中的序号
S = "abcd123"
T1 = "abcd"
T2 = "abcd124"
T1是S的子串,T2不是S的子串,T1位置在S中为0
字符串的前缀是指字符串的任意首部
字符串的后缀是字符串的任意尾部
比如字符串“abbc”的前缀有“a”,“ab”,“abb”,“abbc”,后缀有“c”,“bc”,“bbc”,“abbc”
二、串的存储
1.顺序存储结构
串的顺序存储结构是用一组地址连续的存储单元来存储串中的字符序列的
2.链式存储结构
对应串的链式存储结构,与线性表是相似的,但是由于串的结构的特殊性,结构中的每个元素数据是一个字符,就会存在很大的浪费空间。
因此,一个结点可以存储多个字符,若最后一个结点没有被占满,可以用"#"或者其他非串值字符代替补全
三、模式匹配
模式匹配:给定主串S= "s1s2s3…sn"和模式串T= “t1t2t3…tn”,在S中寻找T的过程称为模式匹配。如果匹配成功,返回T在S中的位置;如果匹配失败,返回-1。
1.BF(Brute Force)算法
BF算法,即暴力(Brute Force)算法
基本思想:从主串S的第一个字符开始和模式串T的第一个字符进行比较,若相等,则继续比较两者的后续字符;否则,从主串S的第二个字符开始和模式T的第一个字符进行比较,重复上述的过程,直到T中的字符全部比较完毕,则说明本趟匹配成功;或者S中的字符全部比较完,则说明匹配失败。
当模式串T与主串S匹配不成功时,主串S和模式串T要进行回溯,模式串T中 j=0主串S中i=i-j+1
主串S=“ababcabcacbab”,模式T=“abcac”
BF算法过程:
(1)第一趟
(2)第二趟
(3)第三趟
(4)第四趟
(5)第五趟
(6)第六趟
算法实现:
1.在串S和串T中设比较的起始下标i和j
2.循环直到S或者T的所有字符均未比较完
如果S[I] == T[j] ,继续比较S和T的下一个字符;
否则,将i和j回溯(i=i-j+1,j=0),进行下一趟比较
3.如果T中所有的字符比较完,则匹配成功,返回匹配的起始比较下标(i-j);
否则,匹配失败,返回-1。
BF算法代码
int BF(char S[],char T[])
{
int i=0,j=0;
while(S[i]!='\0'&&T[j]!='\0') // 任意一个字符串匹配完,循环结束
{
if(S[i]==T[j]) // 匹配成功,继续匹配下一个字符
{
i++;
j++;
}
else // 匹配失败,i,j回溯
{
i=i-j+1;
j=0;
}
}
if(T[j]=='\0') // T匹配成功
return (i-j); // 返回T的位置
else
return -1;
}
int BF(char S[],char T[])
{
int i=0,j=0,start=0; //start用来记录T相对于S的位置
while(S[i]!='\0'&&T[j]!='\0') // 任意一个字符串匹配完,循环结束
{
if (S[i] == T[j]) // 匹配成功,继续匹配下一个字符
{
i++;
j++;
}
else // 匹配失败,i,j回溯
{
start++;
i=start;
j=0;
}
}
if(T[j]=='\0') // T匹配成功
return start; // 返回T的位置
else
return -1;
}
2.KMP算法
BF算法时间性能比较低,因为在每趟匹配不成功时存在大量的回溯,没有利用已经部分匹配的结果,在匹配不成功时主串不进行回溯,模式串需要向右滑动一段距离----KMP算法,可以大大避免重复遍历的情况。
主串S=“ababcabcacbab”,模式T=“abcac”
KMP算法过程
向对于BF算法来说中间的第2,3,4趟以及第5趟前两个字符的比较都是非必要的。
注:移动位数=模式串已匹配的字符数 - 失配位置前的最长前缀匹配字符数,这是模式串T整体移动的距离,next数组的值是模式串T中下标j移动的位置
KMP算法中,指向主串S的变量i不回溯,模式串T向右滑动到新的比较起点K,并且K的值仅仅与模式串T有关,取决于当前模式串字串的前后缀相似度
在需要查找字符串之前,先对要查找的字符串做一个分析,也就是next数组值的推导,提高查找的速度
next数组
计算next[j]方法:
1.当j=0时,next[j] = -1 ;
当next[j] = -1 表示不进行字符比较
2.当j>0时,next[j] = MAX{k|1<k<j-1 ,且’P1…Pk-1’ = 'Pj-k+1…Pj-1 ’
next[j]值为模式串的位置从0到j-1构成的的串中所出现首尾相同的子串的最大长度
3.当无首尾相同的子串时next[j]的值为0
next[j]=0表示从模式串的头部开始进行字符比较
模式串T='abcdabd’的next数组
可能失配位:0 1 2 3 4 5 6
模式串T :a b c d a b d
j=0: next[0]=-1
j=1: 字串:'a' next[j] = 0
j=2: 字串:'ab' next[j] = 0
j=3: 字串:'abc' next[j] = 0
j=4: 字串:'abcd' next[j] = 0
j=5: 字串:'abcda' next[j] = 1
j=6: 字串:'abcdab' next[j] = 2
前缀'ab' 和 后缀'ab'相等,长度为2,next[j]=2
所以,next[j]={-1,0,0,0,0,1,2}
next数组的递归求解思路:
j代表前缀,i代表后缀,A1 = A2,A1的长度为j,若A1和A2继续向后增加一个字符是否会相等呢?
若相等T[i] == T[j],则next[i+1] 就等于新的匹配长度j+1。
if(j==-1||T[i]==T[j]) //j==-1,不进行字符串比较,S,T后移一位
{ //若T[i]==T[j],则next[i+1] = j+1
i++;
j++;
next[i]=j;
}
若不相等T[i] != T[j],就只能从已知除了A1,A2之外的最长的B1,B3来讨论
else //T[i]!=T[j],回溯到除A1,A2长的最长字符串处
{
j=next[j];
}
j=next[j], 把j移动到绿色的色块的位置,继续讨论B1,B3分别往后的字符串是否还相等,
next数组实现代码
void getNext(char *T,int *next)
{
int j=-1; //j前缀
int i=0; //i后缀
next[0]=-1;//j=0,next[j]=-1
while(T[i]!='\0')
{
if(j==-1||T[i]==T[j]) //j==-1,不进行字符串比较,S,T后移一位
{ //若T[i]==T[j],则next[i+1] = j+1
i++;
j++;
next[i]=j;
}
else //T[i]!=T[j],回溯到除A1,A2长的最长字符串处
{
j=next[j];
}
}
}
注:next数组的长度应该定义为字符串长度+1
主串S=“abcdabeabcdab” 模式串T=“abcdabd”
KMP算法过程
j=6匹配失败,模式串T移动位数 = 模式串已匹配的字符数 - 失配位置前的最长前缀匹配字符数 = 6 - 2 =4,所以模式串T移动4位,但是比较的开始的地方要看next[j]的值,这里next[j=6]=2,所以从模式串T下标为2处开始比较
T移动距离 = 2 - 0 = 2,j = next[j=2] = 0
j = next[j=0] = -1,不进行字符比较,同时向后移动一位
KMP算法伪代码描述
1.在主串S和模式串T中分别设比较的起始下标i和j
2.循环直到S或者T的所以字符均未比较完
如果S[i] == T[j] ,继续比较S和T的下一个字符
否则,将j向右移动到next[j]位置,即 j=next[j],
T移动的位数 = T已经匹配的字符数 - 失配位置前的最长前缀匹配字符数
如果j =-1,则将j,i分别加1,准备下一趟比较
3.如果T中所有字符均比较完毕,则返回匹配的起始下标,否则返回-1、
KMP算法代码
int KMP(char S[],char T[])
{
int i=0,j=0;
int lenS=strlen(S);
int lenT=strlen(T);
while(i<lenS&&j<lenT)
{
if(j==-1||S[i]==T[j]) // 字符匹配成功或者j==-1(不进行比较),继续下一个字符匹配
{
i++;
j++;
}
else // 匹配失败,j进行回溯
{
j=next[j];
}
}
if(lenT==j) //匹配成功返回T的位置
return i-j;
else
return -1;
}
总结
提示:这里对文章进行总结: