Алгоритм Колусси — различия между версиями
| Kabanov (обсуждение | вклад) | Kabanov (обсуждение | вклад)  м | ||
| Строка 8: | Строка 8: | ||
| Отметим, что нумерация символов строк и элементов массива у нас начинается с <tex>0</tex>.   | Отметим, что нумерация символов строк и элементов массива у нас начинается с <tex>0</tex>.   | ||
| − | Множество всех позиций шаблона разделим на два (дизъюнктных) непересекающихся множества. Тогда каждая попытка сравнения шаблона с исходной строкой после очередного сдвига состоит из двух  | + | Множество всех позиций шаблона разделим на два (дизъюнктных) непересекающихся множества. Тогда каждая попытка сравнения шаблона с исходной строкой после очередного сдвига состоит из двух случаев. | 
| {{Определение | {{Определение | ||
| |definition= | |definition= | ||
| − | В  | + | В первом случае сравнения выполняются слева направо с символами текста, выровненными с шаблоном в позиции, для которой значение функции <tex>\mathrm{Kmp}(i)</tex> строго больше <tex>-1</tex>. Такие позиции будем называть '''насыщенными''' (''noholes''). | 
| }} | }} | ||
| {{Определение | {{Определение | ||
| |definition= | |definition= | ||
| − | + | Во втором случае будут производиться сравнения в оставшихся позициях справа налево. Такие позиции будем называть '''ненасыщенными''' (''holes''). | |
| }} | }} | ||
| Такая стратегия предоставляет, как минимум, 2 преимущества: | Такая стратегия предоставляет, как минимум, 2 преимущества: | ||
| − | * когда несовпадение появляется во время  | + | * когда несовпадение появляется во время первого случая, после соответствующего сдвига уже нет необходимости делать проверки в насыщенных позициях, которые были проверены на предыдущем шаге. | 
| − | * когда несовпадение появляется во время  | + | * когда несовпадение появляется во время второго случая, это означает, что суффикс шаблона совпал с подстрокой исходной строки <tex>y</tex> и после соответствующего сдвига префикс шаблона будет все ещё совпадать с этой подстрокой, поэтому нет необходимости в повторной проверке. | 
| {{Определение | {{Определение | ||
| Строка 62: | Строка 62: | ||
| Кроме того <tex>x[h[k]]=y[j’+h[k]]</tex> для <tex>0 \leqslant k < \mathrm{first}(h[r]-\mathrm{K_{min}}(h[r]))</tex> означает, что сравнения могут продолжены с <tex>x[h[\mathrm{first}(h[r] - \mathrm{K_{min}}(h[r]))]]</tex> и <tex>y[j'+h[\mathrm{first}(h[r]-\mathrm{K_{min}}(h[r]))]]</tex>. | Кроме того <tex>x[h[k]]=y[j’+h[k]]</tex> для <tex>0 \leqslant k < \mathrm{first}(h[r]-\mathrm{K_{min}}(h[r]))</tex> означает, что сравнения могут продолжены с <tex>x[h[\mathrm{first}(h[r] - \mathrm{K_{min}}(h[r]))]]</tex> и <tex>y[j'+h[\mathrm{first}(h[r]-\mathrm{K_{min}}(h[r]))]]</tex>. | ||
| − | ==  | + | == Второй случай == | 
| Теперь рассмотрим ситуацию, когда <tex>x[h[k]]=y[j+h[k]]</tex> для <tex>0 \leqslant k < r</tex> и <tex>x[h[r]] \neq y[j+h[r]]</tex> для <tex>nd \leqslant r < m</tex>.   | Теперь рассмотрим ситуацию, когда <tex>x[h[k]]=y[j+h[k]]</tex> для <tex>0 \leqslant k < r</tex> и <tex>x[h[r]] \neq y[j+h[r]]</tex> для <tex>nd \leqslant r < m</tex>.   | ||
| Строка 146: | Строка 146: | ||
|        return rmin |        return rmin | ||
| − | Функция для построение массива <tex>\mathrm{ | + | Функция для построение массива <tex>\mathrm{shift}</tex>. | 
|     '''int'''[] buildShift('''int'''[] kmin, '''int'''[] rmin, '''int'''[] h, '''int''' nd, '''int''' m) |     '''int'''[] buildShift('''int'''[] kmin, '''int'''[] rmin, '''int'''[] h, '''int''' nd, '''int''' m) | ||
|        '''int''' shift[m + 1] |        '''int''' shift[m + 1] | ||
| Строка 156: | Строка 156: | ||
|        '''return''' shift |        '''return''' shift | ||
| − | Функция для построения массива <tex>\mathrm{ | + | Функция для построения массива <tex>\mathrm{next}</tex>. | 
| − | + |    '''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 | ||
| ==Асимптотики== | ==Асимптотики== | ||
Версия 17:24, 13 июня 2014
Алгоритм, разработанный Ливио Колусси, профессором итальянского университета 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
