Алгоритм Колусси

Материал из Викиконспекты
Перейти к: навигация, поиск

Алгоритм

Отметим, что нумерация символов строк и элементов массива у нас начинается с [math]0[/math].

Обозначим за [math]Kmp[/math]префикс-функцию, но при этом она определена для [math]x[0] \dots x[m-1][/math] и имеет значение [math]-1[/math] по умолчанию.

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


Определение:
В первой фазе сравнения выполняются слева направо с символами текста, выровненными с шаблоном в позиции, для которой значение функции [math]\mathrm{Kmp}(i)[/math] строго больше [math]-1[/math]. Такие позиции будем называть насыщенными (noholes).


Определение:
Вторая фаза состоит в сравнении в оставшихся позициях справа налево. Такие позиции будем называть ненасыщенными (holes).


Такая стратегия предоставляет, как минимум, 2 преимущества:

  • когда несовпадение появляется во время первой фазы, после соответствующего сдвига уже нет необходимости делать проверки в насыщенных позициях, которые были проверены на предыдущем шаге.
  • когда несовпадение появляется во время второй фазы, это означает, что суффикс шаблона совпал с периодом (factor) строки, после соответствующего сдвига префикс шаблона будет все ещё совпадать с периодом текста, поэтому нет необходимости в повторной проверке.


Определение:
Обозначим за [math]\mathrm{K_{min}}(i) = \min\{k : \ x[0 \dots i-1-k]=x[d \dots i-1]\ and\ x[i-k] \neq x[i]\}[/math]. Функция [math]\mathrm{K_{min}}[/math] определена для всех позиций [math]i[/math], у которых [math]Kmp(i) \neq -1[/math].


Если [math]\mathrm{K_{min}}(i) \neq 0[/math], то периодичность шаблона [math]x[/math] заканчивается в позиции [math]i[/math].

Очевидно, что для [math]0 \lt i \lt m[/math] позиция [math]i[/math]:

  • насыщенная, если [math]\mathrm{K_{min}}(i-1) \neq 0[/math]
  • ненасыщенная, иначе

Обозначим за [math]nd+1[/math] количество насыщенных позиций в шаблоне [math]x[/math].

Массив [math]h[/math] содержит первыми элементами [math]nd+1[/math] насыщенных позиций в возрастающем порядке и затем [math]m-nd-1[/math] ненасыщенных в убывающем порядке, т.е.

  • для всех [math]0 \leqslant i \leqslant nd[/math] [math]h[i][/math] насыщенная позиция и [math]h[i] \lt h[i+1][/math] для [math]0 \leqslant i \lt nd[/math].
  • для всех [math]nd \lt i \lt m[/math] [math]h[i][/math] ненасыщенная и [math]h[i] \gt h[i+1][/math] для [math]nd \lt i \lt m-1[/math].


Определение:
Обозначим за [math]\mathrm{R_{min}}(i)[/math] наименьший период [math]r[/math] шаблона [math]x[/math] большего, чем [math]i[/math]. Функция [math]\mathrm{R_{min}}[/math] определена для всех позиций [math]i[/math], у которых [math]Kmp(i) = -1[/math].


Определение:
Обозначим за [math]\mathrm{first}(u)[/math] наименьший число [math]v[/math] такое, что [math]u \leqslant h[v][/math].


Допустим, что шаблон [math]x[/math] выровнен с [math]y[j \dots j+m-1][/math].

Первая фаза

Рассмотрим случай, когда [math]x[h[k]]=y[j+h[k]][/math] для [math]0 \leqslant k \lt r \lt nd[/math] и [math]x[h[r]] \neq y[j+h[r]][/math].

Пусть [math]j' = j + \mathrm{K_{min}}(h[r])[/math].

Тогда нет вхождений шаблона [math]x[/math], начиная с [math]y[j \dots j'][/math] и [math]x[/math] может быть сдвинут на [math]\mathrm{K_{min}}(h[r])[/math] позиций вправо.

Кроме того [math]x[h[k]]=y[j’+h[k]][/math] для [math]0 \leqslant k \lt \mathrm{first}(h[r]-\mathrm{K_{min}}(h[r]))[/math] означает, что сравнения могут продолжены с [math]x[h[\mathrm{first}(h[r] - \mathrm{K_{min}}(h[r]))]][/math] и [math]y[j'+h[\mathrm{first}(h[r]-\mathrm{K_{min}}(h[r]))]][/math].

Вторая фаза

Теперь рассмотрим ситуацию, когда [math]x[h[k]]=y[j+h[k]][/math] для [math]0 \leqslant k \lt r[/math] и [math]x[h[r]] \neq y[j+h[r]][/math] для [math]nd \leqslant r \lt m[/math].

Пусть [math]j' = j + \mathrm{R_{min}}(h[r])[/math] позиций вправо.

Тогда нет вхождений шаблона [math]x[/math], начиная с [math]y[j \dots j'][/math] и [math]x[/math] может быть сдвинут на [math]\mathrm{R_{min}}(h[r])[/math].

Кроме того [math]x[0 \dots m-1-\mathrm{R_{min}}(h[r])]=y[j' \dots j+m-1][/math] означает, что сравнения могут продолжены с [math]x[h[\mathrm{first}(m-1-\mathrm{R_{min}}(h[r]))]][/math] и [math]y[j'+h[\mathrm{first}(m-1-\mathrm{R_{min}}(h[r]))]][/math].

Предварительные вычисления

Для вычисления значений [math]\mathrm{K_{min}}[/math] будем использовать вспомогательную функцию [math]\mathrm{H_{max}}[/math].

Определение:
Обозначим за [math]\mathrm{H_{max}}[/math] функцию, для которой выполняется:
  • [math]x[k \dots \mathrm{H_{max}}(k)-1]=x[0 \dots \mathrm{H_{max}}(k)-k-1][/math],
  • [math]x[\mathrm{H_{max}}(k)] \neq x[\mathrm{H_{max}}(k)-k][/math].


Псевдокод

Наивный вариант

  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

Явная реализация по определению, очевидно, работает за [math]O(m^2)[/math] и требует [math]O(m)[/math] памяти.

Улучшенный вариант

  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

На каждой итерации цикла увеличивается либо переменная [math]i[/math], либо [math]k[/math] (или переменная [math]q[/math], которая используется в конечном счете для обновления [math]k[/math]). Поскольку [math]i = 1[/math] и [math]k = 1[/math] в начале и [math]i \lt k = m + 1[/math] в конце алгоритма, количество итераций алгоритма не превосходит [math]2 \cdot m[/math]. Следовательно функция требует [math]O(m)[/math] времени и памяти.

Функция для построения массива [math]\mathrm{K_{min}}[/math].

  int[] buildKmin(int[] hmax, int m)
     int kmin[m]
     for i = m .. 1
        if hmax[i] < m
           kmin[hmax[i]] = i
     return kmin

Функция для построения массива [math]\mathrm{R_{min}}[/math].

  int[] buildRmin(int[] hmax, int[] kmin, int m)
     int rmin[m]
     int r = 0
     for i = m - 1 .. 0
        if hmax[i + 1] == m
           // [math]r[/math] — первое число большее, чем [math]i[/math] и такое, что шаблон имеет период [math]r[/math]
           r = i + 1
        if kmin[i] == 0
           rmin[i] = r
        else
           rmin[i] = 0
     return rmin

Функция для построение массива [math]\mathrm{Shift}[/math].

  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

Функция для построения массива [math]\mathrm{Next}[/math].


Асимптотики

  • partitions the set of pattern positions into two disjoint subsets; the positions in the first set are scanned from left to right and when no mismatch occurs the positions of the second subset are scanned from right to left;
  • preprocessing phase in [math]O(m)[/math] time and space complexity;
  • searching phase in [math]O(n)[/math] time complexity;
  • performs [math]3/2n[/math] text character comparisons in the worst case.

Сравнение с другими алгоритмами

Достоинства

  • Поиск выполняется за [math]O(n)[/math] в отличие от алгоритма Кнута-Морриса-Пратта, поиск в котором занимается [math]O(n+m)[/math], что помогает уменьшить константу при [math]m \sim n[/math].
  • Фаза предобработки выполняется за [math]O(m)[/math] в отличие от алгоритма Бойера-Мура, где в наилучшем случае можно получить время [math]O(n+|\Sigma|)[/math], что плохо при больших алфавитах.

Недостатки

  • Сложность реализации.

Источники

  • COLUSSI L., 1991, Correctness and efficiency of the pattern matching algorithms, Information and Computation 95(2):225-251.
  • Colussi algorithm
  • Colussi.ppt