Турбо-алгоритм Бойера-Мура — различия между версиями
Zemskovk (обсуждение | вклад) (→Асимптотика) |
Zemskovk (обсуждение | вклад) (→Асимптотика) |
||
Строка 60: | Строка 60: | ||
* Фаза поиска требует <tex>O(n)</tex> времени. | * Фаза поиска требует <tex>O(n)</tex> времени. | ||
* В худшем случае поиск требует <tex>O(2n)</tex> сравнений. | * В худшем случае поиск требует <tex>O(2n)</tex> сравнений. | ||
− | |proof=Разобьём поиск на стадии. Каждая стадия делится на | + | |proof= Стадия препроцессинга совпадает со стадией препроцессинга в [[Алгоритм Бойера-Мура|алгоритме Бойера-Мура]], поэтому рассмотрим только стадию поиска. |
+ | |||
+ | Разобьём поиск на стадии. Каждая стадия делится на | ||
две операции: сканирование и сдвиг. На этапе <tex>k</tex> мы назовём <tex>suf_k</tex> длину суффикса шаблона | две операции: сканирование и сдвиг. На этапе <tex>k</tex> мы назовём <tex>suf_k</tex> длину суффикса шаблона | ||
что совпадает с текстом. Этому предшествует символ, который не | что совпадает с текстом. Этому предшествует символ, который не | ||
совпадает с соответствующим символом в тексте (в случае когда <tex>suf_k</tex> не соответствует длине шаблона). Мы также | совпадает с соответствующим символом в тексте (в случае когда <tex>suf_k</tex> не соответствует длине шаблона). Мы также | ||
назовём <tex>shift_k</tex> длину сдвига сделанного на этапе <tex>k</tex>. | назовём <tex>shift_k</tex> длину сдвига сделанного на этапе <tex>k</tex>. | ||
+ | |||
Рассмотрим три типа стадий в зависимости от характера сканирования и сдвига. Мы говорим, что сдвиг на стадии <tex>k</tex> короткий, если <tex>2shift_k < suf_k + 1</tex>. Тогда три типа сдвигов будут: | Рассмотрим три типа стадий в зависимости от характера сканирования и сдвига. Мы говорим, что сдвиг на стадии <tex>k</tex> короткий, если <tex>2shift_k < suf_k + 1</tex>. Тогда три типа сдвигов будут: | ||
# Стадия с последующей стадией с прыжком. | # Стадия с последующей стадией с прыжком. | ||
Строка 74: | Строка 77: | ||
В случае стадии типа (1), стоимость соответствует единственному сравнению несовпадающих символов. Другие сравнения, проведенные в течение той же стадии, являются | В случае стадии типа (1), стоимость соответствует единственному сравнению несовпадающих символов. Другие сравнения, проведенные в течение той же стадии, являются | ||
стоимостью последующих этапов. Общее количество сравнений выполняемых алгоритмом это сумма стоимостей. Мы хотим доказать, что <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 cost < 2 \sum shifts</tex>. Во второй <tex> \sum </tex> длину последнего сдвига заменим <tex>m</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>. | ||
Строка 79: | Строка 83: | ||
Можно считать, что на стадии <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>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>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 + 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 + 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>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>, что и требовалось.}} | Это показывает нам, что <tex> \sum cost_k \leqslant 2 \sum shift_k</tex>, что и требовалось.}} | ||
Версия 21:42, 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), то , а затем , что даже лучше, чем ожидалось. Если на стадии мы имеем , то мы получим то, что ожидалось: . Последняя ситуация для рассмотрения, когда на стадии мы имеем . Это означает, что, как уже упоминалось ранее, обычный сдвиг применяется на стадии .Таким образом, приведенный выше анализ также применяется на стадии , и, так как только случай (а) может произойти тогда мы получаем . Мы, наконец, получаем . Последний аргумент, доказывающий первый шаг индукции: если все стадии до таковы, что , то .Пусть Это показывает нам, что первый этап после этапа такой, что . Целое число существует потому, что иначе получим бесконечную последовательность сдвигов с уменьшающейся длиной. После этого мы получим . , что и требовалось. |