Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
algorithms.doc
Скачиваний:
29
Добавлен:
06.12.2018
Размер:
9.73 Mб
Скачать
  1. Лекция 14

  1. Поиск строк

Пусть имеется последовательность символов S={ si } из алфавита : si , i=1,…,N и последовательность W={ wi } из алфавита : wi , i=1,…,M, MN.

Ставится задача поиска всех таких целых 0kN-M, что для всех i=1,…,M: sk+i=wi .

Стандартной интерпретацией данной задачи является задача поиска заданного слова в строке или задача поиска слова в файле.

У данной задачи существует прямое решение, при котором происходит последовательная проверка совпадения подстроки W со всеми подряд идущими подстроками строки S длины M. Легко увидеть, что данный алгоритм требует времени порядка (MN) (реализация данного алгоритма приведена в следующем параграфе). На самом деле задачу можно решить существенно быстрее, о чем и пойдет речь далее.

    1. Отступление на тему языка с. Ввод-вывод строк из файла

Стандартной интерпретацией поставленной задачи является задача поиска заданного слова в текстовом файле. В ОС UNIX имеется стандартная программа grep поиска слов по шаблону в текстовых файлах. Ее простейший формат вызова следующий:

grep шаблон список_файлов_поиска

здесь вместо слова шаблон можно подставить просто слово, которое требуется найти в тексте файлов из списка список_файлов_поиска. Имена файлов в списке разделяются пробелом. Если список имен файлов пуст, то слово ищется в стандартном потоке ввода.

Следующая программа демонстрирует, как можно простейшим способом реализовать функцию grep для случая, когда строки в файлах имеют длину не более 512 символов и когда вместо шаблона поиска вводится простое слово.

#include <stdio.h>

#include <string.h>

int main(int npar,char **par)

{FILE *f; int i,istr; char str[512]; if(npar<=1)return -1;

for(i=(npar==2?1:2);i<npar;i++)

{

f=(npar==2?stdin:fopen(par[i],"r"));

if(f)

{

for(istr=1;fgets(str,512,f);istr++)

if(strstr(str,par[1]))

{printf("%s: %d: %s",par[i],istr,str);}

fclose(f);

}

}

return 0;

}

Программа демонстрирует следующие возможности:

  • Передачу параметров из командной строки

  • Открытий/закрытие файлов

  • Ввод текста из файла

  • Использование стандартного потока ввода

  • Стандартную процедуру поиска слова в тексте

Детальное описание всех указанных возможностей следует искать в документации к языку С.

    1. Алгоритм поиска подстроки с использованием хеш-функции (Алгоритм Рабина-Карпа)

Идея алгоритма проста: для каждой подстроки Si строки S, используемой при сравнении c W (т.е. подстроки длины, равной длине W), вычисляется значение некоторой хэш-функции h(Si). Если h(Si)= h(S), то данная подстрока является хорошим претендентом на равенство и для нее производится полное сравнение, иначе переходим к следующей подстроке Si+1. При вычислении h(Si) мы можем использовать тот факт, что строка Si отличается всего на два символа от строки Si-1, поэтому есть шанс использовать уже вычисленное значение h(Si-1) для вычисления h(Si). Действительно, это можно сделать, если в качестве хэш-функции использовать остаток от деления на некоторое число K. При этом строка должна интерпретироваться как одно большое целое число. Действительно

Si=(si+0,…, si+M-1) =( si-1, si+0,…, si+M-1)-256M si-1=

=256( si-1, si+0,… , si+M-2) + si+M-1 - 256M si-1

из чего вытекает

Si%K = ( 256 (( si-1, si+0,… , si+M-2) %K) + si+M-1 – (256M%K) si-1 ) %K

h(Si) = ( 256 h(Si-1) + si+M-1 – (256M%K) si-1 ) %K

Единственное большое число, возникающее в последней формуле, это 256M, поэтому (256M%K) следует вычислить заранее. Наконец, если K выбрать таким образом, чтобы 256K<231-257, то (256M%K) si-1<231-257 , 256 h(Si-1) <231-257 и тогда все вычисления могут производиться в рамках обычных целых чисел. Действительно

|256 h(Si-1) + si+M-1 – (256M%K) si-1 |

MAX(|256 h(Si-1) |,|(256M%K) si-1|)+ si+M-1

231-1

что помещается в переменную int.

Осталось заметить, что K должно быть простым числом. В качестве K можно взять K =8388593. Действительно

256K =2147479808

231-257 =2147483391

При идеальном распределении значений хэш-функции каждое ее значение будет появляться с вероятностью 1/K, поэтому время работы алгоритма для неудачного поиска будет складываться из времени предварительных вычислений (M), времени поиска при отсутствии коллизий (N) и времени поиска при наличии коллизий (MN/K). Полное время поиска при наличии в строке S n вхождений строки W будет следующим

T=(M+N+MN/K)+ (Mn)

Итак, мы доказали следующую теорему

Теорема. При идеальном распределении значений хэш-функции в среднем алгоритм Рабина-Карпа требует времени

T=(M+N+MN/K)+ (Mn)

где M – длина искомой подстроки, N – длина строки входных данных, n – количество вхождений искомой строки в строку входных данных, K – модуль, используемый при вычислении остатка от деления в хэш-функции.

В худшем случае алгоритм работает за время

T=(MN).

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]