字符串的模式匹配描述:在字符串T中查找是否有跟另一字符串pat相等的子串。
朴素的模式匹配-----BF算法:利用pat中的字符依次与T中字符依次比较
int Find(string T, string pat, int k){
//在T中从K个字符开始寻找模式串pat在串中的位置,找不到返回-1,找到返回第一次匹配的位置
int i, j;
for(i = k; i <= T.size() - pat.size(); ++i){
for(j = 0; j < pat.size(); ++i){
if(T[i+j] != pat[j]){
break;
}
}
if(j == pat.size()){
return i;
}
}
return -1;
}
这个算法是一种带回溯的算法,一旦比较不等,就将模式pat右移一位,再从p0开始进行比较。若T的长度为n,pat的长度为m,算法的运行时间为O(n×m)
改进的匹配算法-----KMP算法
改进:避免朴素模式匹配中的回溯操作
前缀:只包含首字母、不包含尾字母的所有子串。
后缀:只包含尾字母、不包含首字母的所有子串。
若现在有一字符串aabaaf,求其前缀表:
a -> 0
aa -> 1
aab -> 0
aaba -> 1
aabaa -> 2
aabaaf -> 0
字符串:a a b a a f
前缀表:0 1 0 1 2 0
代表当某位失效时,需要跳到下标为前一字符对应的相等最长前缀长度的位置,如在字符f位置失配,则需要找到字符f前一位字符a对应的前缀表中的数值2,接着跳到下标为2的位置继续下一次匹配。
前缀表有三种表示方式:
1、直接存储: 0 1 0 1 2 0
2、减一后存储: -1 0 -1 0 1 -1
3、添加首位值-1,将前缀表右移一位: -1 0 1 0 1 2 --------------此时字符对应前缀表中的数值,就是该字符失配时需要跳到的位置
前缀表的计算方式
1、计算直接存储方式的前缀表
①初始化
②前缀不相同的处理
③前缀相同的处理
④更新next数组
void getNext(string pat, int next[]){
//i指向后缀末尾位置,j指向前缀末尾位置,同时j还代表i之前包括i的子串的最长相等前后缀长度
//1、初始化
int i, j = 0;
next[0] = 0;
for(i = 1; i < pat.size(); ++i){
//2、前缀末尾、后缀末尾不匹配时,j进行回退,j到0时,不用回退,故j > 0
while(j > 0 && s[i] != s[j]){
j = next[j - 1];
}
//3、前后缀末尾相同的情况
if(s[i] == s[j]){
j++;
}
//4、更新next数组
next[i] = j;
}
}
2、添加首位值-1,将前缀表右移一位
void getNext(string pat, int next[]){
int j = 0, k = -1;
int lengthPat = pat.size();
next[0] = -1;
//计算next[j]
while(j < lengthPat){
if(k == -1 || pat[j] == pat[k]){
j++;
k++;
next[j] = k;
}
else{
k = next[k];
}
}
}
右移一位的next数组的KMP算法实现快速匹配
一般地,若设在进行某一趟匹配比较时在模式pat的第j位失配,如果j>0,那么在下一趟比较时模式串pat的起始比较位置是next(j),目标串T的指针不回溯,仍指向上一趟失配的字符;如果j=0,则目标串T进一,模式串pat回到起始0的位置,继续进行下一次比较。
int fastFind(string T, string pat, int k, int next[])const{
//用模式串pat在目标串T中从k的位置开始寻找,找到则返回pat在T中开始的下标,否则返回-1
int posP = 0; //两个串的扫描指针
int posT = k;
int lengthP = pat.size(); //模式与目标串的长度
int lengthT = T.size();
while(posP < lengthP && posT < lengthT){
if(posP == -1 || pat[posP] == pat[posT]){
posP++;
psoT++;
}
else{
posP = next[posP];
}
}
if(posP < lengthP){
return -1;
}
else{
return posT-lengthP;
}
}