Перейти до основного вмісту

Алгоритм Рабіна–Карпа для пошуку рядка

Цей алгоритм ґрунтується на ідеї хешування, тож якщо ви не знайомі з хешуванням рядків, зверніться до статті хешування рядків.

Цей алгоритм запропонували Рабін і Карп у 1987 році.

Задача: дано два рядки — взірець ss і текст tt. Потрібно визначити, чи трапляється взірець у тексті, і якщо так, то перелічити всі його входження за час O(s+t)O(|s| + |t|).

Алгоритм: обчислюємо хеш для взірця ss. Обчислюємо значення хешів для всіх префіксів тексту tt. Тепер ми можемо порівняти підрядок довжини s|s| зі взірцем ss за сталий час, використовуючи обчислені хеші. Отже, порівнюємо кожен підрядок довжини s|s| зі взірцем. Загалом це займе час O(t)O(|t|). Тому остаточна складність алгоритму становить O(t+s)O(|t| + |s|): O(s)O(|s|) потрібно для обчислення хешу взірця, а O(t)O(|t|) — для порівняння кожного підрядка довжини s|s| зі взірцем.

Коли підходить цей алгоритм?
  • Шукаєте один взірець у тексті й прийнятна ймовірнісна відповідь (рідкісні колізії хешів допустимі)? (якщо потрібен детермінований результатКМП)
  • Взірець лише один, а не цілий словник? (якщо взірців багато одночасно → Ахо-Корасік)
  • Потрібні також інші порівняння довільних підрядків за O(1)O(1)? (тоді корисна загальна техніка → Хешування рядків)

Реалізація

vector<int> rabin_karp(string const& s, string const& t) {
const int p = 31;
const int m = 1e9 + 9;
int S = s.size(), T = t.size();

vector<long long> p_pow(max(S, T));
p_pow[0] = 1;
for (int i = 1; i < (int)p_pow.size(); i++)
p_pow[i] = (p_pow[i-1] * p) % m;

vector<long long> h(T + 1, 0);
for (int i = 0; i < T; i++)
h[i+1] = (h[i] + (t[i] - 'a' + 1) * p_pow[i]) % m;
long long h_s = 0;
for (int i = 0; i < S; i++)
h_s = (h_s + (s[i] - 'a' + 1) * p_pow[i]) % m;

vector<int> occurrences;
for (int i = 0; i + S - 1 < T; i++) {
long long cur_h = (h[i+S] + m - h[i]) % m;
if (cur_h == h_s * p_pow[i] % m)
occurrences.push_back(i);
}
return occurrences;
}

Задачі для практики

Відеоматеріали