Что такое Z-функция
Z-функция строки — это массив, в котором каждый элемент Z[i]
содержит длину наибольшего префикса строки, начинающегося с позиции i
, который также является префиксом всей строки. Рассмотрим пример:
Строка: “abacaba”
Z-функция: [0, 0, 1, 0, 3, 0, 1]
Z[0] = 0
, потому что полный префикс не имеет смысла.Z[1] = 0
, так как префикс, начинающийся с позиции 1 (“bacaba”), не совпадает с началом строки.Z[2] = 1
, так как префикс, начинающийся с позиции 2 (“acaba”), совпадает с “a”.Z[4] = 3
, так как префикс, начинающийся с позиции 4 (“aba”), совпадает с “aba”.
Наивная реализация
Простая реализация Z-функции заключается в том, чтобы для каждой позиции в строке проверить, насколько длинный префикс, начинающийся с этой позиции, совпадает с префиксом всей строки.
Алгоритм:
- Создаем массив
Z
длинойn
(длина строки) и инициализируем его нулями. - Для каждой позиции
i
от 1 доn-1
: 3. Проверяем, насколько длинный префикс строки, начиная сi
, совпадает с началом строки. 4. Сохраняем длину этого префикса вZ[i]
.
Код на C++:
#include <iostream>
#include <vector>
#include <string>
std::vector<int> naiveZFunction(const std::string &s) {
int n = s.length();
std::vector<int> Z(n, 0);
for (int i = 1; i < n; i++) {
while (i + Z[i] < n && s[Z[i]] == s[i + Z[i]]) {
Z[i]++;
}
}
return Z;
}
int main() {
std::string s = "abacaba";
std::vector<int> Z = naiveZFunction(s);
for (int z : Z) {
std::cout << z << " ";
}
return 0;
}
Эффективная реализация
Наивный алгоритм имеет временную сложность O(n^2)
, что не подходит для длинных строк. Существует более эффективный алгоритм с временной сложностью O(n)
, который использует так называемое “правило зеркала”.
Алгоритм:
- Создаем массив
Z
длинойn
и инициализируем его нулями. - Устанавливаем две переменные
L
иR
, которые обозначают границы правого самого сегмента, совпадающего с префиксом строки. - Для каждой позиции
i
от 1 доn-1
: 4. Еслиi
находится за пределами сегмента (i > R
), то делаем то же, что и в наивном алгоритме. 5. Еслиi
находится внутри сегмента (i <= R
), то используем уже вычисленные значения Z-функции для зеркальной позиции относительноL
. 6. Поправляем значение Z[i] с помощью прямого сравнения, если необходимо, и обновляемL
иR
.
Код на C++:
#include <iostream>
#include <vector>
#include <string>
std::vector<int> efficientZFunction(const std::string &s) {
int n = s.length();
std::vector<int> Z(n, 0);
int L = 0, R = 0;
for (int i = 1; i < n; i++) {
if (i <= R) {
Z[i] = std::min(R - i + 1, Z[i - L]);
}
while (i + Z[i] < n && s[Z[i]] == s[i + Z[i]]) {
Z[i]++;
}
if (i + Z[i] - 1 > R) {
L = i;
R = i + Z[i] - 1;
}
}
return Z;
}
int main() {
std::string s = "abacaba";
std::vector<int> Z = efficientZFunction(s);
for (int z : Z) {
std::cout << z << " ";
}
return 0;
}
Таким образом, Z-функция является мощным инструментом для анализа строк и поиска подстрок. Эффективный алгоритм вычисления Z-функции позволяет использовать ее для решения многих задач, связанных с обработкой строк, за линейное время.