LCME Wiki
Advertisement

Общая постановка задачи[]

Пусть дан образец Needle длиной n и строка Haystack. Требуется найти такой индекс i, что:

Либо, если такого индекса не существует, вернуть число, которое не может быть интерпретировано как индекс, например, отрицательное.

Наивный алгоритм[]

Идём вперёд по строке и на каждой итерации проверяем, совпадают ли следующие n символов с Needle.

 let naive_search haystack needle =
  let needle_len = String.length needle in
  let search_end = String.length haystack - needle_len in
  let rec search i =
   if i > search_end then -1 else
   if (String.sub haystack i needle_len) = needle then i else
   search (succ i) in
  search 0

Как нетрудно заметить, в худшем случае на каждой итерации нужно сравнений, всего производится итераций, поэтому наибольшее количество сравнений, которое, возможно, придётся произвести - это , что соответствует .

Алгоритм Рабина-Карпа[]

Хэш-функции[]

Вообще говоря, хэш-функцией называется функция , дающая более-менее равномерное распределение результатов. Кстати, такая функция представлена в библеотеке OCaml'а. Но в сейчас нас интересует более узкое понятие: хэш-функция строки. Самый простой способ её реализовать - это просто просуммировать коды всех символов.

Описание алгоритма[]

Вычислим хэш-функцию от needle и от первых n символов haystack и сравним их. Если совпали, произведём обыкновенное сравнение. Если нет, сдвинем позицию проверки вправо на единицу, вычтем из значения хэш-функции хэш первого символа и прибавим хэш n+1-го. Повторить...

 let hash s =
  let sum = ref 0 in
  for i = 0 to pred (String.length s) do
   sum := !sum + (int_of_char s.[i]);
  done; !sum
 
 let rabin_karp haystack needle =
  let haystack_len = String.length haystack
  and needle_len = String.length needle in
  let search_end = haystack_len - needle_len in
  if search_end < 0 then -1 else
  let needle_hash = hash needle in
  let rec search i h =
   if (i > search_end) then -1 else
   if (h = needle_hash) && ((String.sub haystack i needle_len) = needle) then i else
   if (i = search_end) then -1 else search (succ i)
    (h - (int_of_char haystack.[i]) + (int_of_char haystack.[i + needle_len])) in
  search 0 (hash (String.sub haystack 0 needle_len))

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

Общее описание[]

Рассмотрим сравнение строк на позиции i, где образец сопоставляется с частью текста . Предположим, что первое несовпадение произошло между и , где . Тогда и .

При сдвиге вполне можно ожидать, что префикс (начальные символы) образца P сойдется с каким-нибудь суффиксом (подсловом) текста P. Длина наиболее длинного префикса, являющегося одновременно суффиксом есть префикс-функция от строки P.

 let self_repeat s =
  let len = String.length s in
  let rec sr i =
   if (String.sub s 0 i) = (String.sub s (len - i) i) then i else sr (pred i) in
  if len = 0 then 0 else sr (pred (String.length s))

Таким образом, если при сравнении несовпадение произошло в позиции i, можно сдвигать текущий указатель начала сравнения вперёд не на единицу, а на префикс-функцию от подстроки .

Классический вариант[]

 let kmp haystack needle =
  let build_backw s =
   Array.init (String.length s) (fun i -> self_repeat (String.sub s 0 i)) in
  let backw = build_backw needle
  and needle_len = String.length needle
  and haystack_len = String.length haystack in
  let rec apply i n =
   if n = needle_len then (i - needle_len) else
   if i = haystack_len then (-1) else
   if haystack.[i] <> needle.[n] then
    if n = 0 then apply (succ i) 0 else
                  apply i backw.(n) else
    apply (succ i) (succ n) in
  apply 0 0

Автоматный вариант[]

 let build_fsm needle =
  let needle_len = String.length needle in
  Array.init 256 (fun ch -> Array.init needle_len (fun len ->
   let c = char_of_int ch in
   if needle.[len] = c then succ len else
   self_repeat ((String.sub needle 0 len) ^ (String.make 1 c))))
 
 let automation haystack needle =
  let needle_len = String.length needle
  and haystack_len = String.length haystack
  and auto = build_fsm needle in
  let rec apply i n =
   if n = needle_len then (i - needle_len) else
   if i = haystack_len then (-1) else
   find (succ i) (auto.(int_of_char haystack.[i]).(n)) in
  apply 0 0

Разница между этими двумя вариантами заключается лишь в том, что автомат строится дольше, чем массив откатов (причём в 256 раз), зато его применение несколько эффективнее, так как массив откатов учитывает все совпавшие символы, а автомат, кроме них, ещё один - тот, на котором произошло несоответствие.

Advertisement