Z 算法
对于长度为n的字符串s,定义函数z[i]表示字符串本身s和其后缀s[i, n-1]的最长公共前缀长度(LCP, longest common prefix)。 z被称为s的z函数。z[0]=0。计算z数组的方法一般被称为Z-Algorithm,也称为扩增KMP算法。
- Z函数的朴素实现方式 朴素实现方式的复杂度为
O(n^2),for循环和while循环两层, 举一个最极端的例子,s='aaaaa', 很明显每次while循环在每一个i时都会遍历全部的z,长度也为n,故朴素算法的时间复杂度为O(n^2)。
1 | def z_function_trivial(s): |
- Z函数线性算法
对于字符串匹配,要想提速匹配过程,必须利用前面匹配好的数组,不然达不到加速的目的。 对于特定的i, 我们称区间[i, i + z[i]]是i的匹配段,称为Z-box。 算法中,我们维护右端点最靠右的匹配端。记作[l, r]。根据定义,s[l, r]是s的前缀。在计算z[i],我们保证l<=i。 初始化时,l=r=0。
1 | def z_function(s): |
复杂度分析: 对于内层的while循环,每次执行都会使得r向后移动至少1位,由于r<n-1所以总共会执行n次。 对于外层循环,只有一次线性遍历。总复杂度为O(n)。
应用场景
- 匹配所有子串
1
2
3
4
5
6
7
8def zMatch(p, t):
s = p + '$' + t
Z = z_function(s)
occurrences = []
for i in range(len(p) + 1, len(s)):
if Z[i] >= len(p):
occurrences.append(i - (len(p) + 1))
return occurrences
1 | $ zMatch('abcdabcd', 'abcdefghijklabcdabcd') |