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') |