Skip to main content

Алгоритм Кнута-Морріса-Пратта

Це завдання є класичним прикладом застосування префікс-функції (і, власне, вона була відкрита саме у зв'язку з цим).

Дано текст tt та рядок ss. Потрібно знайти та вивести всі позиції входження рядка ss у текст tt.

Алгоритм

Позначимо для зручності через nn довжину рядка ss, а через mm — довжину тексту tt.

Утворимо рядок s+#+ts + \# + t, де символ #\# — це роздільник, який не повинен зустрічатися ніде більше. Обчислимо для цього рядка префікс-функцію. Тепер розглянемо її значення, окрім перших n+1n+1 (які, як видно, належать до рядка ss і роздільника). За визначенням, значення π[i]\pi[i] показує найдовшу довжину підрядка, що закінчується в позиції ii і збігається з префіксом. Але у нашому випадку це π[i]\pi[i] — фактично довжина найбільшого блоку збігу з рядком ss, що закінчується в позиції ii. Більшою за nn ця довжина бути не може — завдяки роздільнику. А от рівність π[i]=n\pi[i] = n (там, де вона досягається), означає, що в позиції ii закінчується шукане входження рядка ss (тільки не треба забувати, що всі позиції відлічуються у склеєному рядку s+#+ts+\#+t).

Таким чином, якщо в деякій позиції ii виявилося π[i]=n\pi[i] = n, то в позиції i2ni - 2n рядка tt починається наступне входження рядка ss у рядок tt.

Як уже згадувалося при описі алгоритму обчислення префікс-функції, якщо відомо, що значення префікс-функції не перевищуватимуть деякої величини, то достатньо зберігати не весь рядок і префікс-функцію, а лише її початок. У нашому випадку це означає, що потрібно зберігати в пам'яті лише рядок s+#s + \# та значення префікс-функції на ньому, а потім уже зчитувати по одному символу рядок tt і перераховувати поточне значення префікс-функції.

Отже, алгоритм Кнута-Морріса-Пратта вирішує це завдання за O(n+m)O(n+m) часу та O(n)O(n) пам'яті.

Реалізація

TODO: add implementation.

Застосування

TODO: add applications.

Задачі

TODO: add problems.