KMP
KMP算法:
1、violentMatch()
- 如果当前字符匹配成功(即S[i] == P[j]),则i++,j++,继续匹配下一个字符;
- 如果失配(即S[i]! = P[j]),令i = i - (j - 1),j = 0。相当于每次匹配失败时,i 回溯,j 被置为0。
int violentMatch(string s,string p){
int lens = s.length();
int lenp = p.length();
int i = 0,j = 0;
while(i<lens&&j<lenp){
if(s[i]==p[j]){
i++,j++;
}else{
i= i-j+1,j=0;
}
}
if(j==lenp) return i-j;
else return -1;
}
S[10]与P[6]不匹配,所以文本串回溯到S[5],模式串回溯到P[0],从而让S[5]跟P[0]匹配
而S[5]肯定跟P[0]失配。为什么呢?因为在之前第4步匹配中,我们已经得知S[5] = P[1] = B,而P[0] = A,即P[1] != P[0],故S[5]必定不等于P[0],所以回溯过去必然会导致失配。
它利用之前已经部分匹配这个有效信息,保持i 不回溯,通过修改j 的位置,让模式串尽量地移动到有效的位置。即下面的KMP算法
2、KMP:
next 数组各值的含义:代表当前字符之前的字符串中,有多大长度的相同前缀后缀。例如如果next [j] = k,代表j 之前的字符串中有最大长度为k 的相同前缀后缀。
通过代码递推计算next 数组
接下来,咱们来写代码求下next 数组。
基于之前的理解,可知计算next 数组的方法可以采用递推:
- 1. 如果对于值k,已有p0 p1, ..., pk-1 = pj-k pj-k+1, ..., pj-1,相当于next[j] = k。
- 此意味着什么呢?究其本质,next[j] = k 代表p[j] 之前的模式串子串中,有长度为k 的相同前缀和后缀。有了这个next 数组,在KMP匹配中,当模式串中j 处的字符失配时,下一步用next[j]处的字符继续跟文本串匹配,相当于模式串向右移动j - next[j] 位。
举个例子,如下图,根据模式串“ABCDABD”的next 数组可知失配位置的字符D对应的next 值为2,代表字符D前有长度为2的相同前缀和后缀(这个相同的前缀后缀即为“AB”),失配后,模式串需要向右移动j - next [j] = 6 - 2 =4位。
向右移动4位后,模式串中的字符C继续跟文本串匹配。
- 2. 下面的问题是:已知next [0, ..., j],如何求出next [j + 1]呢?
对于P的前j+1个序列字符:
- 若p[k] == p[j],则next[j + 1 ] = next [j] + 1 = k + 1;
- 若p[k ] ≠ p[j],如果此时p[ next[k] ] == p[j ],则next[ j + 1 ] = next[k] + 1,否则继续递归前缀索引k = next[k],而后重复此过程。 相当于在字符p[j+1]之前不存在长度为k+1的前缀"p0 p1, …, pk-1 pk"跟后缀“pj-k pj-k+1, …, pj-1 pj"相等,那么是否可能存在另一个值t+1 < k+1,使得长度更小的前缀 “p0 p1, …, pt-1 pt” 等于长度更小的后缀 “pj-t pj-t+1, …, pj-1 pj” 呢?如果存在,那么这个t+1 便是next[ j+1]的值,此相当于利用已经求得的next 数组(next [0, ..., k, ..., j])进行P串前缀跟P串后缀的匹配。
- PS:卡在这里:
- 若p[k] == p[j],则next[j + 1 ] = next [j] + 1 = k + 1;
- 若p[k ] ≠ p[j],如果此时p[ next[k] ] == p[j ],则next[ j + 1 ] = next[k] + 1,否则继续递归前缀索引k = next[k],而后重复此过程。 相当于在字符p[j+1]之前不存在长度为k+1的前缀"p0 p1, …, pk-1 pk"跟后缀“pj-k pj-k+1, …, pj-1 pj"相等,那么是否可能存在另一个值t+1 < k+1,使得长度更小的前缀 “p0 p1, …, pt-1 pt” 等于长度更小的后缀 “pj-t pj-t+1, …, pj-1 pj” 呢?如果存在,那么这个t+1 便是next[ j+1]的值,此相当于利用已经求得的next 数组(next [0, ..., k, ..., j])进行P串前缀跟P串后缀的匹配。
-
void getNext(string p){ int lenp = p.length(); next[0] = -1; int k = -1,j=0; while(j<lenp-1){ if(k==-1||p[k]==p[j]){ j++; k++; next[j] = k; }else{ k = next[k]; } } }
总体代码:
-
#include<iostream> using namespace std; const int maxn = 1e5+5; int next[maxn]; void getNext(string p){ int lenp = p.length(); next[0] = -1; int k = -1,j=0; while(j<lenp-1){ if(k==-1||p[k]==p[j]){ j++; k++; next[j] = k; }else{ k = next[k]; } } } int violentMatch(string s,string p){ int lens = s.length(); int lenp = p.length(); int i = 0,j = 0; while(i<lens&&j<lenp){ if(s[i]==p[j]){ i++,j++; }else{ i= i-j+1,j=0; } } if(j==lenp) return i-j; else return -1; } int kmp(string s,string p){ int lens = s.length(); int lenp = p.length(); int i=0,j=0; while(i<lens && j<lenp){ if(j==-1||s[i]==p[j]){ i++,j++; }else{ j = next[j]; } cout<<"KMP"<<endl; } if(j==lenp) return i-j; else return -1; } int main() { int ans = 0; string s = "BBCABCDABABCDABCDABDE"; string p = "ABCDABD"; ans = violentMatch(s,p); cout<<ans<<endl; getNext(p); for(int i=0;i<p.length();i++){ cout<<next[i]<<" "; } //ans = kmp(s,p); cout<<ans<<endl; return 0; }