- •Элементарные алгоритмы для работы с графами
- •Работа алгоритма[править | править вики-текст]
- •Неформальное описание[править | править вики-текст]
- •Формальное описание[править | править вики-текст]
- •Алгоритм поиска в глубину[править | править вики-текст]
- •Нерекурсивные варианты[править | править вики-текст]
- •Общая идея
- •[Править]Пошаговое представление
- •[Править]Реализация
- •[Править]Время работы
- •[Править]Цвета вершин
- •Топологическая сортировка и сильно связные компоненты
- •↑ Пример ориентированного неотсортированного графа, к которому применима топологическая сортировка
- •Пример работы алгоритма[править | править вики-текст]
- •Алгоритмы[править | править вики-текст]
- •Минимальные остовные деревья (мst)
- •Введение
- •Постановка задачи
- •Кратчайшие пути
- •Давайте придумаем что-нибудь простое
- •А если я педант?
- •Задача о максимальном потоке
- •[Править]Определение потока
- •[Править]Пример
- •Увеличивающие пути. Теорема Бержа
- •Алгоритм Эдмондса. Сжатие цветков
- •Эффективная реализация
- •Оптимизация: предварительное построение паросочетания
- •Случай двудольного графа
- •Дальнейшая оптимизация
- •[Править]Реализация
- •[Править]Оценка производительности
- •[Править]Пример несходящегося алгоритма
- •[Править]Оценка быстродействия
- •[Править]Литература
- •Максимальные паросочетания[править | править вики-текст]
- •Описание алгоритма Необходимые определения
- •Теорема Бержа
- •Алгоритм Куна
- •Время работы
- •Реализация
- •Улучшенная реализация
- •[Править]Постановка задачи
- •[Править]Решение
- •[Править]Псевдокод
- •Префиксы и суффиксы строки
- •Прямые переходы
- •[Править]Суффиксные ссылки
- •Основные определения и описание структуры[править | править вики-текст]
- •Свойства суффиксных деревьев[править | править вики-текст]
- •Требования суффиксного дерева к памяти[править | править вики-текст]
- •[Править]Недостатки
- •Метод хеширования
- •[Править]Алгоритм
- •[Править]Псевдокод
- •[Править]Время работы
- •[Править]Надёжность
- •Автоматные модели в программировании
- •[Править]Переходы между состояниями [править]Прямые переходы
- •[Править]Суффиксные ссылки
- •[Править]Псевдокод
- •[Править]Пример использования
- •[Править]Псевдокод
- •[Править]Пример
- •[Править]Время работы
- •[Править]Эффективный алгоритм
- •[Править]Псевдокод
- •[Править]Время работы
- •[Править]Построение префикс-функции по z-функции [править]Постановка задачи
- •[Править]Описание алгоритма
- •Приближенные алгоритмы решения np-трудных задач
[Править]Недостатки
Требует операций, вследствие чего алгоритм работает медленно в случае, когда длина паттерна достаточно велика (см. выше).
Алгоритм Рабина-Карпа
Алгоритм Рабина-Карпа
Рабин и Карп изобрели алгоритм поиска подстрок, который эффективен на практике и к тому же обобщается на другие аналогичные задачи. В среднем он работает достаточно быстро.
Предположим для начала, что Х = {0, 1, 2, ..., 9} (в общем случае можно считать, что каждый символ в алфавите Х есть d-ичная цифра, где d= |Х|). Тогда строку из k символов можно рассматривать как десятичную запись числа (k-значного), а сами символы как цифры.
Если P[1..т] — образец, то через p обозначим число, десятичной записью которого он является; аналогично, если T[1..n] — текст, то для всех значений s =0,1,..., n-m обозначим через ts число, десятичной записью которого является строка T[s+ 1 .. s+ т]. Очевидно, s является допустимым сдвигом тогда и только тогда, когда ts= p. Если бы мы могли вычислить p за время О(m) и все ti,-за время O(n), то мы смогли бы найти все допустимые сдвиги за время О(n), сравнивая р со всеми ts по очереди.
Вычислить р за время О(m) действительно можно, используя схему Горнера:
P= Р[m] + 10(Р[m-1 ]+ 10(Р[m-2 ]+ ...+ 10(P[2]+ P[1])...)).
Точно так же за время O(m) можно вычислить t0.
Чтобы вычислить t1,t2,…tn-m за время O(n -m), можно воспользоваться формулой
ts+1= 10(ts-10m-1T[s+1])+T[s+m+1].
До сих пор мы не учитывали того, что числа р и t велики — настолько, что предположение о выполнении арифметических операций за время 0(1) едва ли допустимо. К счастью, эту трудность можно обойти следующим образом: надо проводить вычисления чисел р и t0 по модулю фиксированного числа q. Тогда все числа не превосходят q и можно считать, что число р и все tj будут действительно вычислены за время 0(n+ m). Обычно в качестве q выбирают простое число, для которого 10q помещается в машинное слово - это упрощает программирование вычислений. В общем случае (для алфавита {0, 1, 2, ..., d}) выбирают такое простое число d, что dq помещается в машинное слово (благодаря этому все арифметические операции происходят в пределах одного слова); последнее рекуррентное соотношение приобретает вид
ts+1=(d(ts-T[s+1]h)+T{s+m+1}mod q, где h = dm-1 (mod q).
Вычисления по модулю q хороши всем, кроме одного: из равенства ts = p (mod q) еще не следует, что ts =p. Поэтому, если ts ≠ p (mod q), то сдвиг s заведомо недопустим и о нем можно забыть, а если ts = p (mod q) , то надо еще проверить, совпадают ли строки Р[1..m] и T[s+1..s + m] на самом деле. Если совпадают, то мы нашли вхождение образца в строку, а если не совпадают, то произошло холостое срабатывание (spurious hit). Если простое число q достаточно велико, то можно надеяться, что дополнительные затраты на анализ холостых срабатываний будут невелики.
Метод хеширования
Для
решения задачи удобно использовать
полиномиальный хеш, так его легко
пересчитывать:
,
где
—
это некоторое простое число, а
—
некоторое большое число, для уменьшения
числа коллизий (обычно берётся
или
,
чтобы модуль брался автоматически при
переполнении типов). Стоит обратить
внимание, что если 2 строчки имеют
одинаковый хэш, то они в большинстве
таких случаев равны.
При удалении первого символа строки и добавлении символа в конец считать хеш новой строки при помощи хеша изначальной строки возможно за :
.
.
Получается :
.
