Турбо-алгоритм Бойера-Мура — различия между версиями
Zemskovk (обсуждение | вклад) (→Асимптотика) |
Zemskovk (обсуждение | вклад) (→Асимптотика) |
||
Строка 73: | Строка 73: | ||
* Если стадия <tex>k</tex> имеет тип (2) или (3), стоимость <tex>cost_k = suf_k + 1</tex>. | * Если стадия <tex>k</tex> имеет тип (2) или (3), стоимость <tex>cost_k = suf_k + 1</tex>. | ||
В случае стадии типа (1), стоимость соответствует единственному сравнению несовпадающих символов. Другие сравнения, проведенные в течение той же стадии, являются | В случае стадии типа (1), стоимость соответствует единственному сравнению несовпадающих символов. Другие сравнения, проведенные в течение той же стадии, являются | ||
− | стоимостью последующих этапов. Общее количество сравнений выполняемых алгоритмом это сумма стоимостей. Мы хотим доказать, что <tex> \sum cost < 2 \sum shifts</tex>. Во второй <tex> \sum </tex> длину последнего сдвига заменим <tex>m</tex>. Даже с этим предположением, мы имеем | + | стоимостью последующих этапов. Общее количество сравнений выполняемых алгоритмом это сумма стоимостей. Мы хотим доказать, что <tex> \sum cost < 2 \sum shifts</tex>. Во второй <tex> \sum </tex> длину последнего сдвига заменим <tex>m</tex>. Даже с этим предположением, мы имеем <tex> \sum shifts < |t|</tex>, и если неравенство выполняется, тo <tex> \sum cost < 2|t|</tex>. |
− | <tex> \sum shifts < |t|</tex>, и если неравенство выполняется, тo <tex> \sum cost < 2|t|</tex>. | ||
Для стадии типа (1), <tex>cost_k = 1</tex> очевидным образом меньше, чем <tex>2shift_k</tex>, так как <tex>shift_k > 0</tex>. Для стадии типа (2), <tex>cost_k = suf_k + 1 \leqslant 2 shift_k</tex>, по определению длинных сдвигов. Остается рассмотреть стадии типа (3). Так как в этой ситуации мы имеем <tex>shift_k < suf_k</tex>, единственный вариант, что обычный сдвиг применяется на стадии <tex>k</tex>. Тогда мы запоминаем этот момент. На следующей стадии, <tex>k + 1</tex>, мы что-то запомнили, что приводит к возможному турбо-сдвигу. Ситуация на стадии <tex>k + 1</tex>, основная ситуация, когда турбо-сдвиг возможен. Прежде чем продолжить доказательство, мы сначала рассмотрим два случая и установим неравенства (по стоимости стадии <tex>k</tex>), которые используем позже. | Для стадии типа (1), <tex>cost_k = 1</tex> очевидным образом меньше, чем <tex>2shift_k</tex>, так как <tex>shift_k > 0</tex>. Для стадии типа (2), <tex>cost_k = suf_k + 1 \leqslant 2 shift_k</tex>, по определению длинных сдвигов. Остается рассмотреть стадии типа (3). Так как в этой ситуации мы имеем <tex>shift_k < suf_k</tex>, единственный вариант, что обычный сдвиг применяется на стадии <tex>k</tex>. Тогда мы запоминаем этот момент. На следующей стадии, <tex>k + 1</tex>, мы что-то запомнили, что приводит к возможному турбо-сдвигу. Ситуация на стадии <tex>k + 1</tex>, основная ситуация, когда турбо-сдвиг возможен. Прежде чем продолжить доказательство, мы сначала рассмотрим два случая и установим неравенства (по стоимости стадии <tex>k</tex>), которые используем позже. | ||
* Случай (а): <tex>suf_k + shift_k \leqslant |p|</tex>. По определению турбо-сдвига, мы имеем <tex>suf_k - suf_{k+1} < shift_{k + 1}</tex>. Таким образом, <tex>cost_k = sufk + 1 \leqslant suf_{k+1} + shift_{k+1} + 1 \leqslant shift_k + shift_{k + 1}</tex>. | * Случай (а): <tex>suf_k + shift_k \leqslant |p|</tex>. По определению турбо-сдвига, мы имеем <tex>suf_k - suf_{k+1} < shift_{k + 1}</tex>. Таким образом, <tex>cost_k = sufk + 1 \leqslant suf_{k+1} + shift_{k+1} + 1 \leqslant shift_k + shift_{k + 1}</tex>. | ||
− | * Случай (б): <tex>suf_k + shift_k > |p|</tex>. По определению турбо-сдвига, мы имеем <tex>suf_{k+1} + shift_k + shift_{k + 1} \geqslant m</tex>. Тогда <tex>cost_k \leqslant m \leqslant 2shift_k - 1 + shift_{k + 1}</tex>.}} | + | * Случай (б): <tex>suf_k + shift_k > |p|</tex>. По определению турбо-сдвига, мы имеем <tex>suf_{k+1} + shift_k + shift_{k + 1} \geqslant m</tex>. Тогда <tex>cost_k \leqslant m \leqslant 2shift_k - 1 + shift_{k + 1}</tex>. |
+ | Можно считать, что на стадии <tex>k + 1</tex> случай (б) имеет место, потому что это дает нам верхнюю границу <tex>cost_k</tex> (это верно, если <tex>shift_k \geqslant 2</tex>, случай <tex>shift_k = 1</tex> можно обрабатывать напрямую). Если стадия <tex>k + 1</tex> типа (1), то <tex>cost_{k + 1} = 1</tex>, а затем <tex>cost_k + cost_{k+1} \leqslant 2shift_k + shift_{k+1}</tex>, что даже лучше, чем ожидалось. Если на стадии <tex>k + 1</tex> мы имеем <tex>suf_{k + 1} \leqslant shift_{k + 1}</tex>, то мы получим то, что ожидалось: <tex>cost_k + cost_{k + 1} \leqslant 2shift_k + 2shift_{k + 1}</tex>. | ||
+ | Последняя ситуация для рассмотрения, когда на стадии <tex>k + 1</tex> мы имеем <tex>suf_{k + 1} > shift_{k + 1}</tex>. Это означает, что, как уже упоминалось ранее, обычный сдвиг применяется на стадии <tex>k + 1</tex>. | ||
+ | Таким образом, приведенный выше анализ также применяется на стадии <tex>k + 1</tex>, и, так как только случай (а) может произойти тогда мы получаем <tex>cost_{k + 1} \leqslant shift_{k + 1} + shift_{k + 2}</tex>. Мы, наконец, получаем <tex>cost_k + cost_{k + 1} \leqslant 2shift_k + 2shift_{k + 1} + shift_{k + 2}</tex>. | ||
+ | Последний аргумент, доказывающий первый шаг индукции: если все стадии <tex>k</tex> до <tex>k + j</tex> таковы, что <tex>suf_k > shift_k,... , suf_{k + j} > shift_{k + j}</tex>, то <tex>cost_k + ... + cost_{k + j} \leqslant 2shift_k + ... + 2shift_{k + j} + shift_{k + j + 1}</tex>. | ||
+ | Пусть <tex>k'</tex> первый этап после этапа <tex>k</tex> такой, что <tex>suf_{k'} \leqslant shift_{k'}</tex>. Целое число <tex>k'</tex> существует потому, что иначе получим бесконечную последовательность сдвигов с уменьшающейся длиной. После этого мы получим <tex>cost_k + ... + cost_{k'} \leqslant 2shift_k + ... + 2shift_{k'}</tex>. | ||
+ | Это показывает нам, что <tex> \sum cost_k \leqslant 2 \sum shift_k</tex>, что и требовалось.}} | ||
==См. также== | ==См. также== |
Версия 21:37, 10 апреля 2016
Турбо-алгоритм Бойера-Мура (англ. Turbo Boyer-Moore) является улучшением алгоритма Бойера-Мура. Турбо-алгоритм, разработанный группой учёных во главе с М.Крочемором, предлагает другой подход к коротким алфавитам и заодно решает вторую проблему — квадратичную сложность в худшем случае.
Содержание
Алгоритм
Турбо-алгоритм Бойера-Мура не нуждается в дополнительном препроцессинге и требует только постоянную дополнительную память относительно оригинального алгоритма Бойера-Мура. Он состоит в запоминании сегмента текста, который соответствует суффиксу шаблона во время последней попытки (и только тогда, когда сдвиг хорошего суффикса был выполнен). Эта методика представляет два преимущества:
- Можно перепрыгнуть через этот сегмент.
- Она может позволить выполнение «турбо-сдвига».
Турбо-сдвиг может произойти, если мы обнаружим, что суффикс образца, который сходится с текстом, короче, чем тот, который был запомнен ранее.
Пусть — запомненный сегмент, а — cуффикс, совпавший во время текущей попытки, такой что — суффикс . Тогда — суффикс , два символа и встречаются на расстоянии в тексте, и суффикс длины имеет период длины , а значит не может перекрыть оба появления символов и в тексте. Наименьший возможный сдвиг имеет длину (его мы и называем турбо-сдвигом).Применение турбо-сдвига в случае |v| < |u|
При
, если сдвиг плохого символа больше, то совершаемый сдвиг будет больше либо равен . В этом случае символы и различны, так как мы предположили, что предыдущий сдвиг был сдвигом хорошего суффикса. Тогда сдвиг больший, чем турбо-сдвиг, но меньший совместит и с одним и тем же символом . Значит, если сдвиг плохого символа больше, то мы можем применить сдвиг больший либо равный .Нельзя совместить символы
с одним и тем же символом .Псевдокод
Стадия препроцессинга совпадает со стадией препроцессинга в алгоритме Бойера-Мура.
В сам алгоритм добавляется обработка турбо-сдвигов.
function TBM(char[] x, char[] y, int n, int m) int n = length(y) int m = length(x) int i = 0 int u = 0 int shift = m if (m == 0) return //Предварительные вычисления int bmBc[] = preBmBc(x, m) int bmGs[] = preBmGs(x, m) while (i <= n - m) int j = m - 1 while (j >= 0 and x[j] == y[i + j]) --j if (u != 0 and j == m - 1 - shift) j -= u if (j < 0) print(i) shift = bm_gs[0] u = m - shift else int v = m - 1 - j int turbo_shift = u - v int bc_shift = bm_bc[y[i + j]] - m + j + 1 shift = max(turbo_shift, bc_shift, bm_gs[j + 1]) if (shift == bm_gs[j + 1]) u = min((m - shift), v) else if (turbo_shift < bc_shift) shift = min(shift, (u + 1)) u = 0 i += shift
Асимптотика
Утверждение: |
|
Разобьём поиск на стадии. Каждая стадия делится на две операции: сканирование и сдвиг. На этапе мы назовём длину суффикса шаблона что совпадает с текстом. Этому предшествует символ, который не совпадает с соответствующим символом в тексте (в случае когда не соответствует длине шаблона). Мы также назовём длину сдвига сделанного на этапе . Рассмотрим три типа стадий в зависимости от характера сканирования и сдвига. Мы говорим, что сдвиг на стадии короткий, если . Тогда три типа сдвигов будут:
Идея доказательства состоит в амортизации сравнения со сдвигами. Определим стоимость следующим образом:
В случае стадии типа (1), стоимость соответствует единственному сравнению несовпадающих символов. Другие сравнения, проведенные в течение той же стадии, являются стоимостью последующих этапов. Общее количество сравнений выполняемых алгоритмом это сумма стоимостей. Мы хотим доказать, что . Во второй длину последнего сдвига заменим . Даже с этим предположением, мы имеем , и если неравенство выполняется, тo . Для стадии типа (1), очевидным образом меньше, чем , так как . Для стадии типа (2), , по определению длинных сдвигов. Остается рассмотреть стадии типа (3). Так как в этой ситуации мы имеем , единственный вариант, что обычный сдвиг применяется на стадии . Тогда мы запоминаем этот момент. На следующей стадии, , мы что-то запомнили, что приводит к возможному турбо-сдвигу. Ситуация на стадии , основная ситуация, когда турбо-сдвиг возможен. Прежде чем продолжить доказательство, мы сначала рассмотрим два случая и установим неравенства (по стоимости стадии ), которые используем позже.
Можно считать, что на стадии Это показывает нам, что случай (б) имеет место, потому что это дает нам верхнюю границу (это верно, если , случай можно обрабатывать напрямую). Если стадия типа (1), то , а затем , что даже лучше, чем ожидалось. Если на стадии мы имеем , то мы получим то, что ожидалось: . Последняя ситуация для рассмотрения, когда на стадии мы имеем . Это означает, что, как уже упоминалось ранее, обычный сдвиг применяется на стадии . Таким образом, приведенный выше анализ также применяется на стадии , и, так как только случай (а) может произойти тогда мы получаем . Мы, наконец, получаем . Последний аргумент, доказывающий первый шаг индукции: если все стадии до таковы, что , то . Пусть первый этап после этапа такой, что . Целое число существует потому, что иначе получим бесконечную последовательность сдвигов с уменьшающейся длиной. После этого мы получим . , что и требовалось. |