7.9 ACM-ICPC字符串算法 Z 函数(扩展 KMP)
在算法竞赛中,字符串处理是一个核心主题,特别是在ACM-ICPC比赛中。今天我们将深入探讨Z函数,这是扩展KMP算法的基础,用于字符串匹配。本文将通过理论介绍和示例代码帮助你理解和实现这一重要的算法。
简介
Z函数,也称为Z数组,是一个非常实用的字符串处理工具,用于解决各种字符串匹配问题,包括查找字符串的所有出现位置、字符串压缩和模式匹配等。它通过扩展KMP算法(也称为扩展的Knuth-Morris-Pratt算法)来优化计算过程。
Z函数定义
对于一个字符串 S 的 Z 函数,我们定义 Z[i]
为字符串 S 与从位置 i 到末尾的子串 S[i...] 的最长公共前缀(LCP)的长度。特别地,Z[0]
通常设置为 0 或字符串的长度。
Z函数的计算方法
为了计算Z函数,我们可以使用一种高效的单遍扫描方法,时间复杂度为 O(n)。以下是计算过程:
- 初始化:设置
Z[0] = 0
(或整个字符串的长度),定义区间 [L, R],最初设为 [0, 0]。 - 对于每个
i
从 1 到 n-1:- 如果
i > R
,通过逐字符比较计算Z[i]
,然后更新 [L, R]。 - 如果
i ≤ R
,令k = i - L
,并使用之前的Z[k]
来初步设定Z[i]
:- 如果
Z[k] < R-i+1
,则Z[i] = Z[k]
。 - 如果
Z[k] >= R-i+1
,则需要从R
后面开始比较,更新Z[i]
并调整 [L, R]。
- 如果
- 如果
- 更新 [L, R]:如果通过新计算的
Z[i]
,有i + Z[i] - 1 > R
,则更新L = i
和R = i + Z[i] - 1
。
示例代码
#include <iostream>
#include <vector>
#include <string>
std::vector<int> compute_Z(const std::string& S) {
int n = S.length();
std::vector<int> Z(n, 0);
int L = 0, R = 0, K = 0;
for (int i = 1; i < n; ++i) {
if (i > R) {
L = R = i;
while (R < n && S[R] == S[R - L]) {
R++;
}
Z[i] = R - L;
R--;
} else {
K = i - L;
if (Z[K] < R - i + 1) {
Z[i] = Z[K];
} else {
L = i;
while (R < n && S[R] == S[R - L]) {
R++;
}
Z[i] = R - L;
R--;
}
}
}
return Z;
}
int main() {
std::string text = "abcabcabc";
std::vector<int> Z = compute_Z(text);
std::cout << "Z array: ";
for (int z : Z) {
std::cout << z << " ";
}
std::cout << std::endl;
return 0;
}
应用场景
Z函数可以应用于多种字符串处理问题,例如:
- 搜索字符串模式:通过将模式和目标文本串联起来,我们可以使用Z函数快速找到模式在文本中的所有出现。
- 字符串相似度检查:通过计算Z函数,可以快速判断两个字符串的相似度或者公共前缀长度。
- 数据压缩:对重复出现的模式进行分析和编码时,Z函数提供了一种高效的方式。