字符串匹配
前缀函数(π \pi π 函数)
定义
给定一个长为n n n 字符串S S S ,其前缀函数被定义为一个长度为n n n 的数组π \pi π 。其中π \pi π 的定义是:如果子串s [ 0... i ] s[0...i] s [ 0 . . . i ] 有一对或多对相等的真前缀与后前缀,π [ i ] \pi[i] π [ i ] 等于他们相等的最长长度,没有则为0.
具体实现
遍历字符串,每次都从最大的π \pi π 开始比较。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 vector<int > piFunction (string s) { int n = s.length (); vector<int > pi (n) ; for (int i=0 ;i<n;i++) { for (int j=i;j>=0 ;j--) { if (s.substr (0 ,j)==s.substr (i-j+1 ,i)) { pi[i]=j; break ; } } } return pi; }
优化
当s [ i + 1 ] = s [ π [ i ] ] s[i+1]=s[\pi[i]] s [ i + 1 ] = s [ π [ i ] ] 时很容易得到,对于π [ i + 1 ] \pi[i+1] π [ i + 1 ] 我们知道他最大等于π [ i ] + 1 \pi[i]+1 π [ i ] + 1 ,所以在第二层循环中j j j 可以从π [ i ] + 1 \pi[i]+1 π [ i ] + 1 开始。
当s [ i + 1 ] ! = s [ π [ i ] ] s[i+1]!=s[\pi[i]] s [ i + 1 ] ! = s [ π [ i ] ] 时,我们需要从j 0 = π [ i ] j^0=\pi[i] j 0 = π [ i ] 不断往前寻找j ( k + 1 ) = π [ i + 1 ] j^{(k+1)}=\pi[i+1] j ( k + 1 ) = π [ i + 1 ] ,即s [ i + 1 ] = s [ j k + 1 ] s[i+1]=s[j^{k+1}] s [ i + 1 ] = s [ j k + 1 ] 。通过图片我们可以知道,经过对π \pi π 的维护此时A , B , C A,B,C A , B , C 段是完全相同的,所以当最后一个字符不匹配时代表对于当前的j k j^{k} j k 其后面π [ j − 1 ] \pi[j-1] π [ j − 1 ] 个数都不可能匹配。
这样我们可以将前面j − − j-- j − − (此处为k + + k++ k + + )的过程替换成j = π [ j − 1 ] j=\pi[j-1] j = π [ j − 1 ] 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 vector<int > piFunction (string s) { int n = s.length (); vector<int > pi (n, 0 ) ; for (int i = 1 ; i < n; i++) { int j = pi[i - 1 ]; while (j != 0 && s[i] != s[j]) j = pi[j - 1 ]; if (s[i] == s[j]) j++; pi[i] = j; } return pi; }
KMP
给出一个长为n n n 字符串s s s 和一个文本t t t ,求出s s s 在t t t 中的所有出现。
我们可以构造一个新字符串c u r = s + ′ # ′ + t cur=s+'\#'+t c u r = s + ′ # ′ + t 其中# \# # 为既不出现在s s s 也不出现在t t t 中的字符,然后求出他的π \pi π 函数。由于# \# # 的存在,很容易得到π [ i ] \pi[i] π [ i ] 的值不会超过n n n 。当π [ i ] = n \pi[i]=n π [ i ] = n 时,代表此时s [ 0... n − 1 ] = c u r [ i − n + 1... i ] s[0...n-1]=cur[i-n+1...i] s [ 0 . . . n − 1 ] = c u r [ i − n + 1 . . . i ] 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 pair<int , vector<int >> kmp (string s, string t) { int n = s.length (); int m = t.length (); string cur = s + '#' + t; vector<int > pi = piFunction (cur); vector<int > show; int num = 0 ; for (int i = n; i < m + n + 1 ; i++) { if (pi[i] == n) { num++; show.push_back (i + 1 - n - n); } } return {num, show}; }