Студопедия

КАТЕГОРИИ:


Архитектура-(3434)Астрономия-(809)Биология-(7483)Биотехнологии-(1457)Военное дело-(14632)Высокие технологии-(1363)География-(913)Геология-(1438)Государство-(451)Демография-(1065)Дом-(47672)Журналистика и СМИ-(912)Изобретательство-(14524)Иностранные языки-(4268)Информатика-(17799)Искусство-(1338)История-(13644)Компьютеры-(11121)Косметика-(55)Кулинария-(373)Культура-(8427)Лингвистика-(374)Литература-(1642)Маркетинг-(23702)Математика-(16968)Машиностроение-(1700)Медицина-(12668)Менеджмент-(24684)Механика-(15423)Науковедение-(506)Образование-(11852)Охрана труда-(3308)Педагогика-(5571)Полиграфия-(1312)Политика-(7869)Право-(5454)Приборостроение-(1369)Программирование-(2801)Производство-(97182)Промышленность-(8706)Психология-(18388)Религия-(3217)Связь-(10668)Сельское хозяйство-(299)Социология-(6455)Спорт-(42831)Строительство-(4793)Торговля-(5050)Транспорт-(2929)Туризм-(1568)Физика-(3942)Философия-(17015)Финансы-(26596)Химия-(22929)Экология-(12095)Экономика-(9961)Электроника-(8441)Электротехника-(4623)Энергетика-(12629)Юриспруденция-(1492)Ядерная техника-(1748)

A b a c a b a с a b c a b a c a b a b b

Вывод

Ввод

Пример

Строки

 

Строки являются одной из самых распространенных структур данных. Битовые строки соответствуют числам. Строки цифр образуют телефонные номера, строки символов – слова. Из слов состоят фразы, из фраз – документы и книги.

Программы выполняют самые разнообразные операции со строками. Строки можно сортировать, искать, анализировать. Распространенной задачей является формирование частотного словаря слов. Поиск повторяющихся подстрок лежит в основе многих архиваторов. Для выявления плагиатов находят длинные повторяющиеся подстроки в разных файлах. В данном разделе рассматриваются основные алгоритмы поиска заданной подстроки в тексте.

Многие задачи связаны с проблемой нахождения нужных слов в тексте. Современные программы обработки текста приучили нас к такой удобной возможности, как поиск и замена фрагментов. Конечно, сейчас функции поиска инкапсулированы во многие языки программирования высокого уровня. Чтобы найти строчку в небольшом тексте вы, наверное, используете встроенную функцию. Но если такого рода поиск является ключевой задачей вашей программы, знать принципы организации функций поиска будет совсем нелишне.

В готовых подпрограммах далеко не всегда и не все написано лучшим образом. Во-первых, в стандартных функциях не обязательно используются самые эффективные алгоритмы, а во-вторых, вполне возможно, что вам понадобится изменить стандартное поведение этих функций (например, предусмотреть возможность поиска по шаблону). Наконец, область применения функции поиска не ограничивается одними лишь текстовыми редакторами. Следует отметить использование алгоритмов поиска при индексации страниц поисковым роботом, где актуальность информации напрямую зависит от скорости нахождения ключевых слов в тексте. Работа простейшего спам– фильтра, заключается в нахождении в тексте письма таких сочетаний слов, как ”работа с высокой оплатой” или “похудание за неделю”. Все вышесказанное говорит об актуальности проблемы, затрагиваемой в этом разделе.

Поставим сначала задачу в несколько упрощенном виде.

Поиск подстроки. Заданы две последовательности символов. Нужно определить все вхождения первой из них (назовем ее образец) во вторую (текст).

Ввод изфайла INPUT.TXT. Первая строка файла является образцом и имеет длину от 1 до 255. Вторая строка является текстом и имеет длину от 1 до 2´109.

Вывод в файл OUTPUT.TXT. Вывести через пробел последовательность номеров позиций в тексте, начиная с которых в него входит образец. Нумерация начинается с 1. Если вхождений нет, вывести No.

AA

AAABAA

1 2 5

Из условия ясно, что образец можно запомнить в массиве символов, а текст – нельзя. Однако сначала для удобства предположим, что и образец, и текст хранятся в массивах P и T соответственно. Пусть M – длина образца, N – длина текста (M ≤ N).

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

Самое очевидное решение – сравнить образец с каждой из подстрок длины M, начинающихся в позициях 1, 2, …, N – M + 1. Такой алгоритм носит название последовательного или прямого [19]. Общее количество сравнений символов оценивается величиной O(M(N-M +1)). При заданных размерностях трудоемкость алгоритма допустима, но с ростом M и N быстро растет. Большинство сравнений в действительности лишнее. Например, если в каком-то многомегабайтном файле будет искаться последовательность длиной 100 Кб, то придется ждать очень долго.

Алгоритм Рабина [19] представляет собой модификацию линейного алгоритма. Он основан на весьма простой идее. Вырежем "окошечко" размером M и будем двигать его по тексту. Нас интересует, не совпадает ли слово в "окошечке" с заданным образцом. Сравнивать по всем буквам долго. Вместо этого фиксируем некоторую числовую функцию на словах длины M. Тогда задача сведется к сравнению чисел, что, несомненно, быстрее. Если значения этой функции на слове в "окошечке" и на образце различны, то совпадения нет. Только если значения одинаковы, необходимо проверять последовательно совпадение по буквам.

Этот алгоритм выполняет линейный проход по образцу (M шагов) и линейный проход по всему тексту (N шагов), стало быть, общее время работы есть O (M+N). При этом мы не учитываем временную сложность вычисления хеш-функции, так как, суть алгоритма в том и заключается, чтобы данная функция была настолько легко вычисляемой, что ее работа не влияла на общую работу алгоритма. Тогда время работы алгоритма линейно зависит от размера строки и текста, значит, программа работает быстро. Вместо того, чтобы проверять каждую позицию на предмет соответствия с образцом, мы можем проверять только те, которые ”напоминают” образец. Итак, для того, чтобы легко устанавливать явное несоответствие, будем использовать функцию Hash, которая должна:

· легко вычисляться;

· как можно лучше различать несовпадающие строки;

· Hash(T[i+1, i+m]) должна легко находиться по Hash(T[i, i+m-1]).

Во время поиска будем сравнивать Hash(P) с Hash(T[i, i+M-1]) для i от 1 до N-M +1 включительно. Если обнаруживаем совпадение, то проверяем посимвольно.

Приведем пример удобной для вычисления функции. Заменим все буквы в тексте и образце их кодами, представляющими собой целые числа. Тогда удобной функцией является сумма кодов. При сдвиге "окошечка" нужно добавить новое число и вычесть "пропавшее".

Однако, проблема в том, что искомая строка может быть длинной. А так как каждой строке нужно сопоставить уникальное число, то и чисел должно быть много, а стало быть, числа будут большими, и работать с ними будет также неудобно. Но какой интерес работать только с короткими строками? Разработчики алгоритма придумали, как улучшить этот алгоритм без особых потерь в скорости работы.

Выберем некоторое число C (желательно простое) и некоторое натуральное число X (X< C). Каждое слово длины M будем рассматривать как последовательность целых чисел, заменив буквы их кодами. Эти числа будем считать как коэффициенты многочлена степени M - 1. Вычислим значение этого многочлена по модулю С в точке X. Это и будет одна из функций семейства (для каждой пары C и X получается своя функция). Сдвиг "окошечка" на одну позицию соответствует вычитанию старшего члена, умножению на X и добавлению свободного члена.

Следующее соображение говорит в пользу того, что совпадения не слишком вероятны. Пусть число C фиксировано, а S и T - два различных слова длины M. Тогда им соответствуют различные многочлены. Совпадение значений функции означает, что в точке X эти два различных многочлена совпадают, т.е. их разность обращается в 0. Разность есть многочлен степени M -1 и имеет не более M -1 корней. Таким образом, если M много меньше C, то случайному значению X мало шансов попасть в "неудачную" точку. Строго говоря, время работы всего алгоритма в целом, есть O(M+N+AMN), но значение константы A достаточно невелико, так что сложность работы почти линейная.

Алгоритм Рабина и алгоритм последовательного поиска являются алгоритмами с наименьшими предварительными трудозатратами, поэтому они годятся для использования при решении некоторого класса задач. Однако эти алгоритмы не являются оптимальными, поэтому мы перейдём к следующему классу алгоритмов. Эти алгоритмы появились в результате тщательного исследования алгоритма последовательного поиска.

Алгоритм Кнута – Морриса – Пратта (КМП) [19] более полно использует информацию, полученную во время сканирования (алгоритм последовательного поиска ее просто выбрасывает). После сдвига образца по тексту стоит вести сравнение посимвольно. Если не совпал первый символ, нет смысла переходить к следующему. Хотелось бы сдвигать образец так, чтобы обеспечить совпадение максимального числа начальных символов образца и подстроки из «окошка». Оказывается, это можно обеспечить путем предварительного исследования образца. Будем следовать описанию алгоритма в [3], объясняя некоторые этапы.

Пусть в i -й позиции текста отмечено несовпадение с текущим символом образца, а в предыдущих j позициях символы текста и образца совпадают. Иными словами, Pj+ 1 ≠ Ti, а P1P2... Pj = Ti-jTi-j+1... Ti- 1. На какое максимальное число позиций можно сдвинуть образец, не рискуя пропустить его вхождение в текст? Очевидно, нужно найти в позициях текста от i – j + 1 до i -1 максимальное совпадение с началом образца. Но поскольку текст в этих позициях совпадает с символами образца, то это эквивалентно нахождению самого длинного начала образца P1P2... Pk ( 1 < k < j), которое повторяется в оставшейся части образца, т.е. P 1 P 2... Pk = Ps+ 1 Ps+ 2... Ps+k.

Если сдвинуть образец на s позиций, то мы обеспечим совпадение в позициях текста от i – j + s до i – j + s + k. Докажем, что полное совпадение может быть только в том случае, когда подстрока P 1 P 2... Pk входит как в начало, так и в конец строки P 1 P 2... Pj, т.е. s+k=j. Предположим обратное: после сдвига на s позиций образец полностью совпадает с подстрокой текста и s+k < j, как это показано на рисунке.

 

…… Ti-j. …........... Ti-j+s ………... Ti-j+s+k Ti- 1 Ti

 

P 1 P 2 …….. ... Ps Ps+ 1.... Ps+k Ps+k+ 1Pj Pj+ 1

 

P 1.. ….. Pk Pk+ 1 ….. Pj-s Pj-s+ 1

В силу полного совпадения, Ti-j+s+k = Pk+ 1, а поскольку Ti-j+s+k = Ps+k+ 1, то Ps+k+ 1 = Pk+ 1. Мы получили в позициях от 1 до j образца повторение первых k + 1 начальных символов. Это противоречит выбору самого длинного начала из k символов.

Итак, подстрока P1P2…Pk входит как в начало, так и в конец строки P1P2... Pj. Сдвинем образец сразу на j - k позиций. Поскольку k < j, первые k символов образца и текущего “окошка” совпадут. Это состояние показано на следующем рисунке.

 

…… Ti-j ………… Ti-j+s …………………… Ti- 1 Ti

 

P 1 P 2……… Ps Ps+ 1.. ………………... Ps+k Pj+ 1

 

P 1.. …………………… Pk Pk+ 1

Если Pk+ 1 = Ti, то можно продолжить сравнение, начиная с символа Ti+ 1. Если же Pk+ 1 ≠ Ti, то снова будем находить самое длинное начало образца P1P2…Pr ( 1 < r < k), которое повторяется в последних позициях подстроки P 1 P 2... Pk.

Эти свойства образца можно рассчитать заранее. Если для каждой позиции j образца известна максимальная длина f(j) < j начала образца, совпадающая с концом строки P 1 P 2 …Pj, то возможен сдвиг образца вперед на j - f(j) позиций без последующего возврата назад. Отсутствие возвращений в тексте позволяет не запоминать его в массиве.

Функция f(j) называется функцией возвратов. Она показывает, к какому символу Pf(j) нужно возвратиться в образце, когда Pj+ 1 ≠ Ti. Это эквивалентно сдвигу образца вправо относительно текста на j - f(j) позиций и следующему сравнению Pf(j) с Ti.

Как вычислять функцию возвратов? Очевидно, f (1)=0. Пусть вычислены f (1),…, f (j -1) и f (j -1) = k. Если Pj = Pk+ 1, то конец строки P1P2... Pj совпадает с ее началом длины k +1, поэтому f (j) = k +1. Если Pj ≠ Pk+ 1, то «следующим концом» строки может быть подстрока P 1 P 2 …Pf ( k ) Pf ( k )+1, поскольку именно P1P2... Pf ( k ) является самым длинным концом P 1 P 2... Pk образца, совпадающим с его началом. Если и эта строка не годится, то следующей подстрокой может быть P 1 P 2... Pf(f(k)) +1 и т. д. Таким образом, или будет найдено начало длины r, при котором P 1 P 2... Pr является концом P 1 P 2Pj, и тогда f (j) = r, либо такого начала найдено не будет, и тогда f (j) = 0.

Приведем пример. В первой строке содержится текст, во второй – образец, в третьей значения функции возврата

<== предыдущая лекция | следующая лекция ==>
Алгоритм. Проще всего в двойном цикле перебирать все пары с начальным и конечным номерами и для каждого интервала находить сумму чисел | A b a c a b a b
Поделиться с друзьями:


Дата добавления: 2014-01-11; Просмотров: 458; Нарушение авторских прав?; Мы поможем в написании вашей работы!


Нам важно ваше мнение! Был ли полезен опубликованный материал? Да | Нет



studopedia.su - Студопедия (2013 - 2024) год. Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав! Последнее добавление




Генерация страницы за: 0.009 сек.