1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7

# конспект · шаг 7 из 7

Конспект: наивный поиск, Рабин-Карп, КМП

выжимка - её можно унести в заметки

Понятия

Текст и образец - где ищут (длина n) и что ищут (длина m). Вхождение - позиция, с которой образец читается в тексте целиком. Вхождения могут перекрываться.

Алфавит - набор символов, из которых составлены строки. Чем он меньше, тем дольше образец совпадает со случайным текстом.

Полиномиальный хеш - строка как число по основанию x, взятое по модулю q. Ложное срабатывание - хеши совпали, строки разные.

Грань - начало строки, которое одновременно её конец и короче её самой.

Префикс-функция - π[i] равно длине самой длинной грани куска p[0..i].

Z-функция - z[i] равно числу символов, на которое кусок строки с позиции i совпадает с её началом. Z-блок [l, r) - самое правое из найденных совпадений с началом.

Что унести

ЧТО ПОМНИТ АЛГОРИТМ МЕЖДУ СДВИГАМИ          ХУДШИЙ СЛУЧАЙ       НА ОБЫЧНОМ ТЕКСТЕ
наивный       ничего                         (n − m + 1) · m     около n · k/(k − 1)
Рабин-Карп    хеш окна                       n · m               около n + m
КМП           сколько символов совпало       2n + подготовка m   около n
Z-функция     самое правое совпадение        2(n + m)            около n + m
n = 1000, сравнений символов наивный КМП
"a" * 1000 и "a" * 9 + "b" 9 910 1 991
"a" * 1000 и "a" * 499 + "b" 250 500 1 501
случайный текст, 4 буквы, m = 10 1 328 1 248
случайный текст, 26 букв, m = 10 1 046 1 044

Квадрат у наивного поиска - свойство данных, а не длины. На случайном тексте из k букв выходит k / (k − 1) сравнений на позицию: 2 при двух буквах, 1,04 при двадцати шести.

РАБИН-КАРП: пересчёт хеша при сдвиге окна
h = ((h − код_ушедшего · x^(m−1)) · x + код_пришедшего) mod q

хеши разные   → вхождения ТОЧНО нет
хеши равны    → вхождение ВОЗМОЖНО, сравнить посимвольно
ложных срабатываний около n / q:   q = 13 → 765,   q = 1009 → 9,   q = 1 000 003 → 0   (n = 10 000)
КМП: один шаг на символ текста c
пока k > 0 и c ≠ p[k]:   k = π[k − 1]        откат по образцу, текст на месте
если c = p[k]:           k = k + 1
если k = m:              вхождение;  k = π[m − 1]

индекс    0  1  2  3  4  5  6  7  8  9  10
образец   a  b  r  a  c  a  d  a  b  r  a
π         0  0  0  1  0  1  0  1  2  3  4
z         0  0  0  1  0  1  0  4  0  0  1

Почему 2n. Число k растёт на единицу только при совпадении - не больше n раз. Каждый откат его уменьшает. Убавить нельзя больше, чем прибавили.

Z-ФУНКЦИЯ: одна строка даёт все три случая
z[i] = min(r − i, z[i − l])     если i внутри блока, иначе 0
дальше продлеваем сравнениями;  если i + z[i] > r, блок становится [i, i + z[i])

поиск:   z склейки p + "#" + t,   вхождение там, где z[i] = m
период:  наименьший i, где i + z[i] = n          (или m − π[m − 1])

Ловушки джокера

i + m < n вместо i + m <= n. Последняя позиция не проверяется, вхождение в конце текста теряется.

Прыжок на m после находки. abab в abababababab входит пять раз, а не три. Встроенный count считает без перекрытий - это его документированное поведение.

Срез вместо цикла. t[i:i + m] == p платит m на каждой позиции, даже если первый символ не совпал.

Поверить хешу. Без посимвольной проверки Рабин-Карп при q = 13 находит 766 вхождений вместо одного.

Отрицательный остаток. В C, Java, JavaScript, Go -188 % 11 равно -1. Брать (x % q + q) % q.

x^m вместо x^(m−1). Первое окно считается верно, все следующие - мусор.

if вместо while при откате. aaab даёт 0 1 2 1 вместо 0 1 2 0, и образец «находится» там, где его нет.

π[k] вместо π[k − 1]. Последний совпавший символ имеет индекс k − 1.

k = 0 после находки. Теряются перекрывающиеся вхождения.

Z-функция без min. Значение вылезает за край блока: aaa даёт 0 2 2 вместо 0 2 1.

Забытое обновление Z-блока. Ответ верный, цена квадратичная. Ловится только замером.

Разделитель, который есть в тексте. Совпадение перешагивает через него, z становится больше m.

Куда это ведёт

КМП при расхождении делает цепочку откатов, и один символ текста может стоить нескольких сравнений. В следующей главе эта цепочка считается заранее для каждого символа алфавита - получается автомат, который тратит на символ текста ровно одно обращение к таблице. А потом идея разворачивается: если сравнивать образец с конца, часть текста можно не читать вовсе.

Комментарии

Пока нет комментариев. Будь первым!

Оставить комментарий

Комментарий появится после проверки. Email не публикуется. Войти, чтобы не вводить имя каждый раз.