Алгоритм Z-функции через бин поиск занимает очень много памяти
Пытаюсь решить задачу Z-функции за NlogN (знаю, что есть метод и за N, но пытаюсь реализовать именно NlogN). При больших входных данных кушает очень много оперативной памяти. Думал это рекурсия уходит в бесконечность, но вроде нет (Visual Studio тоже не ругался на бесконечный цикл и зависал от потребляемой памяти). N в 10000 символов решает за 2 секунды, что тоже долго, в 20000 символов 8 секунд, больше 30000 начинает зависать.
Алгоритм решается через бинарный поиск, т.е. на каждой позиции рассматриваем префикс и суффикс равные максимальной длины суффикса. Если они не равны, длина уменьшается вдвое, либо берется позиция между последним хорошим и последним плохим результатами. Если равны и разница между проверяемой длиной и последним плохим результатом 1, то взвращаем длину, иначе проверяем диапазон между длиной и последним плохим результатом.
Собственно код : Вычисление хэшей
class HashStr {
private:
const long long x_ = 257, p = 1000000007;
long long *h, *x, n;
public:
// высчитывает хэш строки
HashStr(string str)
{
n = str.size();
h = new long long[n + 1]; // hashes for string
x = new long long[n + 1];
h[0] = 0;
x[0] = 1;
for (int i = 1; i < n + 1; i++)
{
h[i] = (h[i - 1] * x_ + int(str[i - 1])) % p;
x[i] = (x[i - 1] * x_) % p;
}
}
// Считает равны ли хэши подстрок
bool Is_substrs_equal(int len, int from1, int from2)
{
if (len + from1 > n || len + from2 > n)
{
cout << "Error: Range out!" << endl;
return false;
}
// if (len > 0)
// cout << "Error : Len is not positive!" << endl;
return (h[from1 + len] + h[from2] * x[len]) % p == (h[from2 + len] + h[from1] * x[len]) % p;
}
};
Сам алгоритм :
int FindZBinSearch(HashStr &hash, const int len, const int pos, const int last_good_res = 0, const int last_bad_res = 0)
{
constexpr int StartPosition = 0; // начальная позиция префикса всегда 0
if (len == 0)
return 0;
// сравнение строк
if (!hash.Is_substrs_equal(len, StartPosition, pos))
{
// если не равны строки
int mid = last_good_res > 0 ? last_good_res + (len - last_good_res) / 2 : len / 2; // и у нас еще нет хорошего результата, то длину делим пополам
// если есть хороший результат, мы берем позицию между хорошим результатом и нынешней длиной
return FindZBinSearch(hash, mid, pos, last_good_res, len); // рекурсивно ищем дальше, меняя только длину подстрок и плохой результат
}
else
{
// если же подстроки равны
if (last_bad_res == 0) // и нам повезло и сразу целиком они равны, то просто пишем целиком длину
return len;
if (last_bad_res - len == 1) // нам не повезло, но разница между хорошей и плохой строкой в 1 символ, то нынешняя длина - это наш результат
return len;
int mid = len + (last_bad_res - len) / 2; // если же вообще не повезло, то находим середину между последним плохим результатом и нынешней длиной
return FindZBinSearch(hash, mid, pos, len, last_bad_res); // снова делем тоже самое но уже длина строк проверяется между длиной и плохим результатом, а хороший результат - это нынешняя длина
}
return -1;
}
int FindZ(string str, int position)
{
HashStr hash(str); // вычисляем хэши
unsigned int len = str.size() - position; // максимальная длина от данной позиции и до конца строки
return FindZBinSearch(hash, len, position);
}
vector<int> z_function_per_log(string str)
{
int n = str.size();
vector<int> res(n, 0);
for (int i = 1; i < n; i++)
{
res[i] = FindZ(str, i);
}
return res;
}