数据结构学习笔记(一)——字符串的模式匹配
参考:
北京大学数据结构与算法c++
模式匹配
已有一个目标字符串T,给定模式P,在目标字符串T中搜索与模式P全同的一个字串,并求出T中与P全同匹配的字串(“简称为配串”),并返回其首字符位置
例如:在字符串“hello world”中找到“llo”并返回2
朴素模式匹配
利用穷举法,逐个字符后移匹配,如果该次匹配失败,则需要首字符后移到上次首字符的下一个,在逐个字符进行匹配,时间复杂度大。即尝试所有的可能情况,包含多次冗余比较。
算法一
代码实现:
运行结果:
输出2
算法二
int FindPat_2(string T, string S, int startidx)
{
int Lastidx = T.length() - S.length();
int S_LEN = S.length();
if (startidx > Lastidx)
return -1;
else
{
int i = startidx, j = 0;
while (i < T.length() && j < S_LEN)
{
if (S[j] == T[i])
{
i++;
j++;
}
else
{
i = i - j + 1;
j = 0;
}
if (j >= S_LEN)
return i - j;
}
return -1;
}
}
运行结果:
输出2
效率分析
T长度为n,模式长度为m,m<=n
如果n的游标每一次移动进行比较匹配,则一共需要比较(n-m+1)次
每一次相同匹配所耗费的时间最坏情况下是m
因此整个算法的最坏时间开销估计为O(m*n)
在匹配中,包含很多冗余匹配,在上一次匹配中已经知道后移一位不满足条件,应该将其去除,减少比较次数。
无回溯匹配
暴力法进行匹配时,主串和模式串的指针i,j都需要进行回退,而主串的指针回退位数超过了合理范围,导致比较次数增加。对于一个给定的模式串,其中每个字符都有可能会遇到匹配失败,这时对应的 j 指针都需要回溯,具体回溯的位置其实还是由模式串本身来决定的,和主串没有关系。
next数组特征
模式串中具有一定的规律,该规律有效利用可以减小比较次数
串的前后缀
串的前缀:包含第一个字符,且不包含最后一个字符的子串
串的后缀:包含最后一个字符,且不包含第一个字符的子串
以ababab为例
其其中一个前缀为ababa,其中一个后缀为babab
计算方法是:对于模式串中的某一字符来说,提取它前面的字符串,分别从字符串的两端(前后缀)查看连续相同的字符串的个数(最长长度),在其基础上 +1 ,结果就是该字符对应的值。
每个模式串的第一个字符对应的值为 0 ,第二个字符对应的值为 1 。
字符串 “abcabac” 对应的 next 数组中的值为(0,1,1,1,2,3,2)
如模式串取到倒数第三个字符时匹配失败,则其前面的串为abca,然后比较前后缀,a和a、ab和ca、abc和bca只有一组且长度为1,然后在这个基础上再加上1则对应的next数组值为2。
详见参考
KMP算法(快速模式匹配算法)详解以及C语言实现
//next数组计算
void Next(string A, int* next) {
next[0] = 0;
int i = 0;
int j = 0;
while (i < A.length()) {
if (j == 0 || A[j - 1] == A[i])
{
j++;
i++;
next[i] = j;
}
else
{
j = next[j - 1];
}
cout << "i: " << i << " j: " << j << " next: " << next[j] << endl;
}
}
KMP完整代码实现
KMP算法优化
KMP存在的问题
当主串与模式串中比较的字符不匹配时,模式串回溯到相应的next数组位置,如果此时回溯的字符与原先字符相同,那么新一次的比较也是失效的,多进行了一次无意义的比较。
如模式串aaaab与主串aaacaaaabxxxx进行匹配时,当主串匹配到c时,对应的模式串字符为a,模式串需要根据next数组进行回溯
结果计算aaaab的next数组为0,1,2,3,4;
则应该回溯到第三个字符也就是a,同时a又匹配失败,继续回溯到第二个字符也是a,最后回溯到第一个a,这种回溯是极其冗余的
KMP算法优化-nextval数组
模式串:a,a,a,a,b
next:0,1,2,3,4
nextval的值从左至右看
将相同的字符next值修改为与之前相同字符相同next的值
即:
0,0,0,0,4
例子:


浙公网安备 33010602011771号