Алгоритм Колусси
Алгоритм, разработанный Ливио Колусси, профессором итальянского университета Padova, и опубликованный им в 1991 году, является продолжением работы над оптимизацией алгоритма Кнута-Морриса-Пратта. Предназначен для поиска одной подстроки в нескольких текстах.
Содержание
Алгоритм
Алгоритм сравнивает символы шаблона
один за другим с символами исходной строки . Для сдвигов шаблона относительно исходной строки применяются вспомогательные функции, описанные ниже.Обозначим за префикс-функцию, но при этом она определена для и имеет значение по умолчанию.
—Отметим, что нумерация символов строк и элементов массива у нас начинается с
.Множество всех позиций шаблона разделим на два (дизъюнктных) непересекающихся множества. Тогда каждая попытка сравнения шаблона с исходной строкой после очередного сдвига состоит из двух случаев.
Определение: |
В первом случае сравнения выполняются слева направо с символами текста, выровненными с шаблоном в позиции, для которой значение функции | строго больше . Такие позиции будем называть насыщенными (noholes).
Определение: |
Во втором случае будут производиться сравнения в оставшихся позициях справа налево. Такие позиции будем называть ненасыщенными (holes). |
Такая стратегия предоставляет, как минимум, 2 преимущества:
- когда несовпадение появляется во время первого случая, после соответствующего сдвига уже нет необходимости делать проверки в насыщенных позициях, которые были проверены на предыдущем шаге.
- когда несовпадение появляется во время второго случая, это означает, что суффикс шаблона совпал с подстрокой исходной строки и после соответствующего сдвига префикс шаблона будет все ещё совпадать с этой подстрокой, поэтому нет необходимости в повторной проверке.
Определение: |
Обозначим за | . Функция определена для всех позиций , у которых .
Если , то периодичность шаблона заканчивается в позиции .
Очевидно, что для
позиция :- насыщенная, если ,
- ненасыщенная, в остальных случаях.
Обозначим за
количество насыщенных позиций в шаблоне .Массив
содержит первыми элементами насыщенных позиций в возрастающем порядке и затем ненасыщенных в убывающем порядке, т.е.- для всех насыщенная позиция и для .
- для всех ненасыщенная и для .
Определение: |
Обозначим за | наименьший период шаблона большего, чем . Функция определена для всех позиций , у которых .
Определение: |
Обозначим за | наименьший число такое, что .
Теперь рассмотрим 2 случаях, возможных при очередной попытке сравнения шаблона с подстрокой из текста. Допустим, что шаблон выровнен с подстрокой .
Первая случай
Рассмотрим случай, когда
для и .Пусть
.Тогда нет вхождений шаблона
, начиная с и может быть сдвинут на позиций вправо.Кроме того равенство
для всех означает, что сравнения могут продолжены с символов и .Второй случай
Теперь рассмотрим ситуацию, когда
для и для .Пусть
позиций вправо.Тогда нет вхождений шаблона
, начиная с и может быть сдвинут на .Кроме того равенство
означает, что сравнения могут продолжены с символов и .Предварительные вычисления
Для вычисления значений
будем использовать вспомогательную функцию .Определение: |
Обозначим за
| функцию, для которой выполняется:
Определение: |
Обозначим за | количество насыщенных позиций строго меньших .
Теперь мы можем определить два функции и :
- и для всех ;
- и для всех ;
- и .
Таким образом, при возникновении несовпадения между
и окно сравнения должно быть сдвинуто на и сравнения могут быть продолжены с позиции h[next[r]] шаблона .Псевдокод
Наивный вариант
int[] buildHmax(char[] x, int m): int hmax[m + 1] for k = 1 .. m int i = k while x[i] == x[i - k] i++ hmax[k] = i return hmax
Явная реализация по определению, очевидно, работает за
и требует памяти.Улучшенный вариант
int[] buildHmax(char[] x, int m): int hmax[m + 1] int i = 1 int k = 1 while k <= m while x[i] == x[i - k] i++; hmax[k] = i int q = k + 1 while hmax[q - k] + k < i hmax[q] = hmax[q - k] + k q++ k = q if k == i + 1 i = k return hmax
На каждой итерации цикла увеличивается либо переменная
, либо (или переменная , которая используется в конечном счете для обновления ). Поскольку и в начале и в конце алгоритма, количество итераций алгоритма не превосходит . Следовательно функция требует времени и памяти.Функция для построения массива
.int[] buildKmin(int[] hmax, int m) int kmin[m] for i = m .. 1 if hmax[i] < m kmin[hmax[i]] = i return kmin
Функция для построения массива
.int[] buildRmin(int[] hmax, int[] kmin, int m) int rmin[m] int r = 0 for i = m - 1 .. 0 if hmax[i + 1] == m //— первое число большее, чем и такое, что шаблон имеет период r = i + 1 if kmin[i] == 0 rmin[i] = r else rmin[i] = 0 return rmin
Функция для построение массива
.int[] buildShift(int[] kmin, int[] rmin, int[] h, int nd, int m) int shift[m + 1] for i = 0 .. nd shift[i] = kmin[h[i]] for i = nd + 1 .. m - 1 shift[i] = rmin[h[i]] shift[m] = rmin[0] return shift
Функция для построения массива
.int[] buildNext(int[] kmin, int[] rmin, int[] h, int nd, int m) // Вычисление массива nhd0 int nhd0[m] int s = 0 for i = 0 .. m - 1 nhd0[i] = s if kmin[i] > 0 ++s // Вычисление массива next int[] next = new int[m + 1] for i = 0 .. nd next[i] = nhd0[h[i] - kmin[h[i]]] for i = nd + 1 .. m - 1 next[i] = nhd0[m - rmin[h[i]]] next[m] = nhd0[m - rmin[h[m - 1]]] return next
Асимптотики
- Фаза предварительных вычислений занимает времени и памяти;
- В худшем случае поиск требует сравнений.
где
— длина исходного текста, — длина шаблонаСравнение с другими алгоритмами
Достоинства
- Поиск выполняется за алгоритма Кнута-Морриса-Пратта, поиск в котором занимается , что помогает уменьшить константу при . в отличие от
- Фаза предобработки выполняется за алгоритма Бойера-Мура, где в наилучшем случае можно получить время , что плохо при больших алфавитах. в отличие от
Недостатки
- Сложность реализации.
Источники
- COLUSSI L., 1991, Correctness and efficiency of the pattern matching algorithms, Information and Computation 95(2):225-251.
- Colussi algorithm
- Colussi.ppt