KMP
应用:求字符串s在文本T中出现的次数与位置
概念:
后缀 从某个位置 i 开始到整个串末尾结束的一个特殊子串。字符串 S 的从 i 开头的后缀表示为 Suffix(S, i),也就是 Suffix(S, i) = S[i…|S|-1]。
真后缀 除了 S 本身的 S 的后缀
前缀 从串首开始到某个位置 i 结束的一个特殊子串。字符串 S 的以 i 结尾的前缀表示为 Prefix(S, i),也就是 Prefix(S, i) = S[0…i]。
真前缀 除了 S 本身的 S 的前缀
Border 如果一个串s’同时是 S 的真前缀和真后缀,那么称s’是S的一个border
原理:nxt【i】为s【0-i】最长相等前缀和与后缀和.对于nxt【i+1】,最大为nxt【i】+1,此时s【i+1】==s【nxt【i】+1】。那么否则呢,如何找到i+1前一部分与s【】开头一部分最大重合?
现在来看这一组字符串
观察i的前面nxt【i-1】=5,可知A=B,同位代换,D=E.而在nxt【A】=2,即C=D,所以C=E.这就找到了最大重合,又发现s【nxt【A】+1】==s【i】,所以nxt【i】=nxt【A】+1,而A=nxt【i-1】。
就这样,整个函数最难懂的地方就出来了while(j>0&&s[i]!=s[j])j=nxt[j];
上代码
void get_nxt(const char s[]){ int len=strlen(s);for(int i=1,j=0;i<len;++i){ while(j>0&&s[i]!=s[j]) j=nxt[j-1]; if(s[i]==s[j]) ++j; nxt[i]=j; } }
nxt已准备就绪,现在解决T中s的个数
大多数人编了两个函数,但是duck不必,我们灵活运用get_nxt()
为了简便起见,我们用 n 表示字符串 s 的长度,用 m 表示文本 t 的长度。
我们构造一个字符串 s+#+t,其中 # 为一个既不出现在 s 中也不出现在 t 中的分隔符。接下来计算该字符串的前缀函数。现在考虑该前缀函数除去最开始n+1 个值(即属于字符串 s 和分隔符的函数值)后其余函数值的意义。
根据定义, nxt[i] 为右端点在 i 且同时为一个前缀的最长真子串的长度,具体到我们的这种情况下,其值为与 s 的前缀相同且右端点位于 i 的最长子串的长度。
由于分隔符的存在,该长度不可能超过 n。而如果等式 nxt[i]=n 成立,则意味着 s 完整出现在该位置(即其右端点位于位置 i)。注意该位置的下标是对字符串 s+#+t 而言的。
因此如果在某一位置 i 有 nxt[i]=n 成立,则字符串 s 在字符串 t 的 i-n+1-(n+1)=i-2n 处出现。
最后是用函数单独求个数,出现位置,适用于在文本T中查找多个s
上代码
int coun(char s[],char t[]){ int len=strlen(s),lem=strlen(t),ans=0; for(int i=0,j=0;i<lem;++i){ while(j>0&&t[i]!=s[j]) j=nxt[j-1]; if(t[i]==s[j]) ++j; if(j==n) num[++ans]=i-n+2,j=nxt[j]; }return ans;
特别注意
当数组以下标0开始时,j=nxt[j-1];以1开始时,j=nxt[j]