Z-算法是什么?

Z算法核心

和KMP算法类似,Z算法大核心也是那一个数组,或者叫z函数。

首先我们定义一下什么叫z数组。

一句话说:在一个字符串里面,从当前符号匹配字符串开头,能匹配到的最大长度。

举个例子:

s: abcabca
z: 0004001

需要注意的是z[0]=0。为什么z[3]=4?可以把从i=3这个字符串抽出来,我们比较

s:  abcabca
i=3 abca  

可以看到从i=3开始,这个和s前缀比较最大长度就是4。

如何得到z数组

首先我们来看暴力法怎么做:

vector<int> z_brute_force(string s) {
	auto N = s.size();
	vector<int> z(N);
	for(int i = 0; i < N; i++) {
		// 这里的z[i]可以理解为前缀的一个长度,i+z[i]就是当前点i,和对应长度的字符,和前缀的比较。如果相等,就可以一直往后走下去
		while (i + z[i] < n && s[z[i]] == s[i + z[i]]) {
			z[i]++;
		}
	}
	return z;
}

暴力法的时间复杂度是$O(n^2)$,但类似于KMP算法,我们也可以利用之前已经得到的信息,就可以优化这个时间。

我们可以维护一个[l, r]的范围,在这个范围里面,我们是知道这一段和前缀是匹配的。

但i在这个范围内的时候,我们是知道这个范围的信息的,因此可以把这个范围的信息给利用起来。

那么我们在更新z[i]的时候,就可以更快。z[i]的上限是r-i,z[i-1]。 因此:

z[i] = min(r - i, z[i - l]);

完整C++实现:

vector<int> z_function(string s) {
	auto N = s.size();
	vector<int> z(N);
	int l = 0, r = 0;
	for(int i = 1; i < N; ++i) {
		if(i < r) {
			z[i] = min(r - i, z[i - l]);
		}
		// 如果不在已知的范围内,那么久退回到暴力法
		while (i + z[i] < n && s[z[i]] == s[z[i - l]]) {
			z[i]++;
		}
		// 如果超过了这个
		if(i + z[i] > r) {
			l = i;
			r = i + z[i];
		}
	}
	return z;
}