简单的模式匹配算法
子串的定位操作通常称为串的模式匹配,它求的是子串(常称模式串)在主串中的位置。这里采用定长顺序存储结构,给出一种不依赖于其他串操作的暴力匹配算法。
// 朴素模式匹配算法int Index2(SString S, SString T) {int k = 1; // 指向主串开始匹配的位置int i = k; // 主串匹配时的匹配位置int j = 1; // 子串匹配时对应的位置while (i <= S.length && j <= T.length) {if (S.ch[i] == T.ch[j]) { // 相等检查下一个字符++i;++j;} else { // 有不相等检查下一个子串k++;i = k; // 如果不设置k,i=i-j+2j = 1;}}// 如果因为主串匹配完导致结束需要判断子串是否刚好匹配// 如果j超出边界说明子串得到匹配if (j > T.length) {return k;} else {return 0;}}
在上述算法中,分别用计数指针 和
指示主串
和模式串
中当前正待比较的字符位置。算法思想为:从主串
的第一个字符起,与模式
的第一个字符比较,若相等,则继续逐个比较后续字符;否则从主串的下一个字符起,重新和模式的字符比较;以此类推,直至模式
中的每个字符依次和主串
中的一个连续的字符序列相等,则称匹配成功,函数值为与模式
中第一个字符相等的字符在主串
中的序号,否则称匹配不成功,函数值为零。将主串中与模式串长度相同的子串搞出来,挨个与模式串对比当子串与模式串某个对应字符不匹配时,就立即放弃当前子串,转而检索下一个子串。
若 为模式串长度,
为主串长度,则
- 匹配成功的最好时间复杂度为:
#card=math&code=O%28m%29&id=ldpN7)
- 匹配失败的最好时间复杂度为:
%3DO(n-m)%5Capprox%20O(n)#card=math&code=O%28n-m%2B1%29%3DO%28n-m%29%5Capprox%20O%28n%29&id=Dfc81)
- 匹配成功的最坏时间复杂度为:
%5Capprox%20O(nm)#card=math&code=O%28nm-m%5E2%2Bm%29%5Capprox%20O%28nm%29&id=Vpy9W)
最坏情况:每个子串的前 个字符都和模式串匹配,只有第
个字符不匹配;
比较好的情况:每个子串的第 1 个字符就与模式串不匹配。
KMP算法
由D. E.Knuth,J.H.Morris和 V.R.Pratt 提出,因此称为KMP算法
推荐直接看视频理解思想:数据结构,KPM算法
概况是对朴素模式匹配算法的优化,通过引入 next 数组来减少回溯。
// KMP算法思想int Index_KMP(SString S, SString T, int next[]) {int i = 1, j = 1;while (i <= S.length && j <= T.length) {if (j == 0 || S.ch[i] == T.ch[j]) {++i;++j;} else {j = next[j]; // 模式串向右移动}}if (j > T.length) {return i - T.length;} else {return 0;}}
求模式串的next数组
- 串的前缀:包含第一个字符,且不包含最后一个字符的子串。
- 串的后缀:包含最后一个字符,且不包含第一个字符的子串.
next 数组手算方法:当第 j 个字符匹配失败,由前 1~j-1 个字符组成的串记为 S,则:next[j] = S 的最长相等前缀长度 +1,特别的 next[1] = 0;此外存在 next[2] 时 next[2] = 1。
KMP算法性能分析
// 获取next数组void get_next(SString T, int next[]) {int i = 1, j = 0;next[1] = 0;while (i < T.length) {if (j == 0 || T.ch[i] == T.ch[j]) {++i;++j;next[i] = j;} else {j = next[j];}}}// KMP算法实现int Index_KMP(SString S, SString T) {int i = 1, j = 1;int next[T.length + 1];get_next(T, next);while (i <= S.length && j <= T.length) {if (j == 0 || S.ch[i] == T.ch[j]) {++i;++j;} else {j = next[j];}}if (j > T.length) {return i - T.length;} else {return 0;}}
KMP 算法平均时间复杂度:#card=math&code=O%28n%2Bm%29&id=KpUy4)
KMP算法优化
next 数组在某些情况下尚有缺陷,还可以进一步优化。
// nextval
void get_nextval(SString T, int nextval[]) {
int i = 1, j = 0;
nextval[1] = 0;
while (i < T.length) {
if (j == 0 || T.ch[i] == T.ch[j]) {
nextval[i] = j;
if (T.ch[i] != T.ch[j]) {
nextval[i] = j;
} else {
nextval[i] = nextval[j];
}
} else {
j = nextval[j];
}
}
}
