KMP 算法
概述
在 s1 中寻找 s2,暴力算法在遇到字符不匹配的时候会令 s2 回溯到起点,s1 回溯到起点后面一格
这里发生失配,然后 j(s2 的元素指针)回溯到 0,i(s1 的元素指针)回溯到 1
显然这种回溯方式做了许多多余比较,KMP做法则是根据next数组做回溯
发生失配后,j 从 3 回溯到 1
前置知识
前后缀
字符串的前缀是从以第一个字符开始,任意元素结尾(除了字符串末尾元素外)组成的字符串的子字符串,字符串的后缀是以最后一个字符为结尾,任意字符(除了字符串起始元素外)开始的子字符串,相等的前缀和后缀的最大长度为最大相等前后缀长度,如果原字符串只有 1 个字符,最大相等前后缀长度视作 0
求 next 数组
从上面的表格可以看出,假如 s2 从 i 号跳转到 j 号,必须保证从 j 向前到开头的这一部分和从 i 向前相同长度的这一部分是完全相同的,也就是说 i 前面的一段字符串的最长相等前后缀的前缀的末尾的后面一位就是 j,由前缀的性质可知,j 同时也表示 i 之前的字符串的最大相等前后缀长度
1 2 3 4 5 6 7 8 9 10 11 12 13
| int i = 0, j = -1; next[0] = -1; while(i<s2.length()-1) { if(j==-1 || s2[i]==s2[j]) { i++,j++; next[i] = j; } else j = next[j]; }
|
Kmp 主程序
求出 next 数组之后只需要将 s2 与 s1 逐个字符匹配,如果失配,就让 s2 按照 next 数组回溯
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| int i = 0,j = 0; while(i<s1.length()) { if( j==-1 || s1[i]==s2[j]) { i++,j++; if(j==s2.length()) break; } else j = next[j]; } if(j==s2.length()) return i - j ; else return -1;
|
瑕疵与优化
考虑 s1 == “aaaaaaaabaaaaac” s2 == “aaaaac”的情况,c 失配后回溯到 4 号 a,又失配,回溯到 3 号 a,然后回溯到 2 号 a…. 一直到 0 号 a 失配,回溯到-1,同一个字符失配过一次后就不需要再尝试匹配,在求 next 的过程中,加入判断语句,判断失配元素与其回溯元素是否相等,若相等,就将回溯元素的回溯元素传递给失配元素
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| int i = 0, j = -1; next[0] = -1; while(i<s2.length()-1) { if(j==-1 || s2[i]==s2[j]) { i++,j++; if(s2[i]!=s2[j]) next[i] = j; else next[i] = next[j]; } else j = next[j]; }
|
AC 自动机
概述
字典树与 KMP 的结合,用于模式串过多,字典树运行时间过长的情况
代码逻辑
- AC 自动机下标 0 为特意设定的头结点
- 回溯数组的求法:让每个模式串的首字母回溯到 0,每一个结点 x 都回溯到与其存储相同字母的结点 bk,bk 父结点为 x 父结点的回溯结点,因为回溯的结点要么是 0 结点,要么是和自己存储相同字母的结点,所以从 bk 走到 0 的字符串必然和 x 走到 0 过程中经历相同长度的字符串的内容相同
核心代码_自动机结构体
1 2 3 4 5 6
| struct node { int bk; int cae; int letter[26]; }AC[maxn];
|
核心代码_建立字典树
1 2 3 4 5 6 7 8 9 10 11
| void BuildTrie(const std::string& s) { int x=0; for(int i=0;i<s.size();i++) { if(!AC[x].letter[s[i]-'a']) AC[x].letter[s[i]-'a'] = ind++; x=AC[x].letter[s[i]-'a']; } AC[x].cae++; }
|
核心代码_求回溯数组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| void GetBtk() { std::queue<int> q; for(int i=0;i<26;i++) { if(AC[0].letter[i]) { AC[AC[0].letter[i]].btk=0; q.push(AC[0].letter[i]); } } while(q.size()) { int cur=q.front(); q.pop(); for(int i=0;i<26;i++) { if(AC[cur].letter[i]) { AC[AC[cur].letter[i]].btk = AC[AC[cur].btk].letter[i]; q.push(AC[cur].letter[i]); } else AC[cur].letter[i] = AC[AC[cur].btk].letter[i]; } } }
|
核心代码_字符串匹配
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| int AC_Query() { int cur=0,ans=0; for(int i=0;i<s.size();i++) { cur = AC[cur].letter[s[i]-'a']; for(int t=cur;t&&AC[t].cae!=-1;t=AC[t].btk) { ans += AC[t].cae; AC[t].cae = -1; } } return ans; }
|