算法要求:判断主串中是否存在子串。
简单模式匹配算法
字符串的匹配算法中,最简单的方法就是暴力破解或者说简单模式匹配(又称朴素模式匹配算法)。
算法描述
从主串的第一个元素开始到主串的最后一个元素结束对子串进行匹配,如果在这个过程中匹配到子串则返回主串中首个匹配到的位置,否则返回-1;
具体实现(C++)
//王道改编版
int Index(string &str,string &substr){
int i = 0, j = 0;
while(i<str.size()&&j<substr.size()){
if(str[i]==substr[j]){
i++;
j++;
}else {
i = i - j + 1;
j = 0;
}
}
if(j==substr.size())
return i-j;
return -1;
}
//自己写的
int Index_simple(string str,string substr){
for (int i = 0; i < str.size();i++){
int j,k=i;
for (j = 0; j < substr.size();j++,k++){
if(str[k]!=substr[j])
break;
}
if(j==substr.size())
return i;
}
return -1;
}
复杂度为O(nm) 其中n为主串长度,m为子串长度。
KMP算法
在简单模式匹配算法中没有利用好主串中对子串已经匹配好的字符信息,所以每次对子串匹配时都需要从主串的下一个位置进行从头匹配。
KMP算法就是利用了这个信息进行了一定的优化。
KMP取自提出算法的三个人的名字开头。(D.E.Knuth,J.H.Morris,V.R.Pratt)
KMP算法需要提前获取子串的next数组,next数组中存放了子串下一次进行比较的字符位置。next数组的获取是通过子串已匹配字符之间进行比较获得,如果str[i]==str[j],表示已匹配字符串中有前缀和后缀相同的部分,则当前next数组(i+1)为相同字符的位置j的下一个位置(j+1),否则利用已完成的next进行下一次匹配,直到next=0(即,没有相同的前后缀字符串);
next数组的实质是获取已匹配的字符串的最长相同前后缀字符串的位置信息。
kmp算法实现
int Index_kmp(string& str,string& substr,vector<int> &next){
int i = 0, j = 0;
while(i<str.size()&&j<substr.size()){
//使用vscode进行执行的时候需要把size()额外设置变量,不然会出错 可以设设成strsize和subsize
if(j<0||str[i]==substr[j]){
//如果相同或者说子串首字符比较失败,i和j往后移
i++;
j++;
}else {
//匹配失败,用next输出子串下一次比较位置
j = next[j];
}
}
if(j==substr.size())
//匹配成功
return i-j;
return -1;
}
next数组获取(C++)
void get_next(string &str, vector<int> &next){
next[0] = -1;
int i = 0, j = -1;
//刚开始时没有匹配好的字符,所以j为-1
while(i<str.size()){
//通过i,j让子串与已匹配的字符进行比较
if(j<0||str[i]==str[j]){
i++;
//i+1代表在kmp中匹配失败的字符位置
j++;
//j+1表示下次需要比较的位置
next[i] = j;
}else {
j = next[j];
}
}
}
next数组还有优化的空间。其中当匹配失败时,且子串substr[j]==substr[next[j]],那么此时的next[j]所指向的子串字符是与匹配失败时的字符一致的,这种比较是豪无意义的。
改进:如果出现了substr[j]==substr[next[j]]则将next[j]修正为next[next[j]],直至两者字符不同为止。称改进后的next数组为nextval。
改进后的next数组
void get_nextval(string &str,vector<int>&nextval){
nextval[0] = -1;
int i = 0, j = -1;
//刚开始时没有匹配好的字符,所以j为-1
while(i<str.size()){
//通过i,j让子串与已匹配的字符进行比较
if(j<0||str[i]==str[j]){
i++;
//i+1代表在kmp中匹配失败的字符位置
j++;
//j+1表示下次需要比较的位置
//当子串i位置字符匹配失败,判断nextval数组是否需要修正
if(str[j]!=str[i])
//当前字符与已匹配字符不相同所无需修正
nextval[i] = j;
else
//当前字符与已匹配字符相同所需要修正为上一个nextval
nextval[i] = nextval[j];
//由于是从前往后,所以nextval可以保证str[j]!=str[nextval[j]]
}else {
j = nextval[j];
}
}
}
kmp算法复杂度为O(n+m) n为子串长度,m为主串长度,其中n为获取next数组的开销,m为匹配子串的开销
(在下算法小白,如有错误,还请指正)