Сколько прочтёт Хорспул
Прежде чем ставить алгоритм поиска в свою программу, хочется знать, сколько работы он сделает на ваших данных, а не на чужом замере. Посчитайте это для алгоритма Хорспула — упрощённого Бойера — Мура, на котором держатся быстрые поиски в стандартных библиотеках.
Текст t длины n, образец p длины m. Окно — место, куда приложен образец; первое окно начинается в позиции 0. В окне с началом s алгоритм сравнивает p[m − 1] с t[s + m − 1], потом p[m − 2] с t[s + m − 2] и так справа налево, пока буквы равны; каждая проверка пары букв — одно сравнение. Если совпали все m букв, это вхождение. Потом, совпало или нет, окно сдвигается на d(c), где c = t[s + m − 1] — буква текста под последней клеткой окна:
- если c встречается среди первых m − 1 букв образца, d(c) = m − 1 − j, где j — самая правая такая позиция;
- если не встречается, d(c) = m.
Алгоритм останавливается, когда окно перестаёт помещаться в текст. Для каждого образца выведите, сколько окон он проверил, сколько сделал сравнений и сколько нашёл вхождений. Среди тестов — начало «Войны и мира» и перевода Мод, геном фага лямбда и пример из статьи Мура «HERE IS A SIMPLE EXAMPLE».
Вход
В первой строке — текст, от 1 до 100 000 знаков (UTF-8, без переводов строки; пробелы — обычные знаки). Во второй — число образцов q (1 ≤ q ≤ 20). В каждой из следующих q строк — образец, от 1 до 100 знаков. Позиции считаются в знаках, а не в байтах. Тесты такие, что на каждый уходит меньше миллиона сравнений.
Выход
Для каждого образца — строка из трёх чисел: окон, сравнений, вхождений.
Примеры
Пример 1
HERE IS A SIMPLE EXAMPLE 3 EXAMPLE SIMPLE E
5 15 1 5 14 1 24 24 5
Пример 2
abracadabra abracadabra 3 abra cad zzz
7 19 4 9 13 2 7 7 0
Ограничения
- Время · Python
- 4000 мс
- Время · Go
- 1000 мс
- Память
- 256 МБ
- Проверка
- 8 тестов
Разбор
Открывайте по ступеням: каждая следующая подсказывает больше. Можно сразу к решению — но задача, решённая с одной подсказкой, запоминается лучше.
1Подсказка 1
Это моделирование, а не поиск ответа хитрым способом: ответ и есть ход алгоритма. Осталось сделать его ровно так, как сказано, и быстро находить d(c).
2Подсказка 2
Таблицу сдвигов стройте один раз на образец: проходите первые m − 1 букв слева направо и записывайте d[p[j]] = m − 1 − j. Правые вхождения перезапишут левые. Чем грозит пройти все m букв?
3Идея
Последняя буква образца в таблицу не входит. Она и так стоит под последней клеткой окна; если бы она попала в таблицу, её сдвиг был бы 0, и окно, которое кончается этой буквой, никогда не сдвинулось бы. Нужна предыдущая копия этой буквы в образце — она и даёт верный сдвиг.
4Разбор
Таблица — словарь «буква → сдвиг» по первым m − 1 буквам, по умолчанию m. Дальше цикл по окнам: сравнения справа налево до первого несовпадения, подсчёт, сдвиг по букве под последней клеткой.
Посмотрите на ответы. На «Войне и мире» Хорспул делает примерно одно сравнение на окно, а окон тем меньше, чем длиннее образец: для «князь Василий» — около 6000 окон на 60 000 знаков. Однобуквенный образец «а» проверяет каждую позицию, как наивный поиск. А в седьмом тесте образец «b» и девятнадцать «a» на тексте из одних «a» делает почти 400 000 сравнений на 20 000 знаков: окно каждый раз сравнивает 19 совпадающих букв, спотыкается на «b» и сдвигается на одну позицию, потому что под последней клеткой снова «a». Наивный поиск здесь в двадцать раз быстрее: он начинает сравнение с «b».
4+Решение
5Сложность
Таблица — O(m). Поиск — O(n · m) в худшем случае, как в седьмом тесте, и около n/m окон на обычном тексте с большим алфавитом.
6Типичные ошибки
- Таблица по всем m буквам. Последняя буква получает сдвиг 0, его «чинят» до 1, и окна, кончающиеся этой буквой, ползут по одной позиции. На «SIMPLE» в первом примере получается шесть окон вместо пяти.
- Сдвиг по несовпавшей букве. Это правило Бойера — Мура, а не Хорспула: у Хорспула сдвиг всегда считается по букве под последней клеткой окна, даже если несовпадение случилось левее.
- Байты вместо знаков. В UTF-8 русская буква — два байта, и окон и сравнений станет больше. Читайте строки как знаки (в Go —
[]rune).