Алгоритм Колусси — различия между версиями
Kabanov (обсуждение | вклад) м |
Kabanov (обсуждение | вклад) |
||
| Строка 1: | Строка 1: | ||
| − | Алгоритм, разработанный Ливио Колусси, профессором итальянского университета Padova, и опубликованный им в 1991 году, является продолжением работы над оптимизацией [[Алгоритм Кнута-Морриса-Пратта|алгоритма Кнута-Морриса-Пратта]]. Предназначен для поиска одной подстроки в | + | Алгоритм, разработанный Ливио Колусси, профессором итальянского университета Padova, и опубликованный им в 1991 году, является продолжением работы над оптимизацией [[Алгоритм Кнута-Морриса-Пратта|алгоритма Кнута-Морриса-Пратта]]. Предназначен для поиска одной подстроки в одном тексте. |
==Алгоритм== | ==Алгоритм== | ||
| Строка 90: | Строка 90: | ||
* <tex>\mathrm{shift}(m)=\mathrm{R_{min}}(0)</tex> и <tex>\mathrm{next}(m)=\mathrm{nhd0}(m-\mathrm{R_{min}}(h[m-1]))</tex>. | * <tex>\mathrm{shift}(m)=\mathrm{R_{min}}(0)</tex> и <tex>\mathrm{next}(m)=\mathrm{nhd0}(m-\mathrm{R_{min}}(h[m-1]))</tex>. | ||
| − | Таким образом, при возникновении несовпадения между <tex>x[h[r]]</tex> и <tex>y[j+h[r]]</tex> окно сравнения должно быть сдвинуто на <tex>shift(r)</tex> и сравнения могут быть продолжены с позиции h[next | + | Таким образом, при возникновении несовпадения между <tex>x[h[r]]</tex> и <tex>y[j+h[r]]</tex> окно сравнения должно быть сдвинуто на <tex>\mathrm{shift}(r)</tex> и сравнения могут быть продолжены с позиции <tex>h[\mathrm{next}(r)]</tex> шаблона <tex>x</tex>. |
==Псевдокод== | ==Псевдокод== | ||
| + | Функция для построения массива <tex>\mathrm{H_{max}}</tex>. | ||
'''Наивный вариант''' | '''Наивный вариант''' | ||
'''int'''[] buildHmax('''char'''[] x, '''int''' m): | '''int'''[] buildHmax('''char'''[] x, '''int''' m): | ||
| Строка 158: | Строка 159: | ||
Функция для построения массива <tex>\mathrm{next}</tex>. | Функция для построения массива <tex>\mathrm{next}</tex>. | ||
'''int'''[] buildNext('''int'''[] kmin, '''int'''[] rmin, '''int'''[] h, '''int''' nd, '''int''' m) | '''int'''[] buildNext('''int'''[] kmin, '''int'''[] rmin, '''int'''[] h, '''int''' nd, '''int''' m) | ||
| − | // Вычисление массива nhd0 | + | <font color="green">// Вычисление массива <tex>\mathrm{nhd0}</tex></font> |
'''int''' nhd0[m] | '''int''' nhd0[m] | ||
'''int''' s = 0 | '''int''' s = 0 | ||
'''for''' i = 0 .. m - 1 | '''for''' i = 0 .. m - 1 | ||
| − | + | nhd0[i] = s | |
| − | + | if kmin[i] > 0 | |
++s | ++s | ||
| − | // Вычисление массива next | + | <font color="green">// Вычисление массива <tex>\mathrm{next}</tex></font> |
| − | '''int''' | + | '''int''' next[m + 1] |
'''for''' i = 0 .. nd | '''for''' i = 0 .. nd | ||
next[i] = nhd0[h[i] - kmin[h[i]]] | next[i] = nhd0[h[i] - kmin[h[i]]] | ||
| Строка 174: | Строка 175: | ||
next[m] = nhd0[m - rmin[h[m - 1]]] | next[m] = nhd0[m - rmin[h[m - 1]]] | ||
'''return''' next | '''return''' next | ||
| + | |||
| + | Основная функция алгоритма Колусси. | ||
| + | '''void''' COLUSSI('''char'''[] x, '''char'''[] y) | ||
| + | '''int''' n = length(y) | ||
| + | '''int''' m = length(x) | ||
| + | |||
| + | <font color="green">// Предварительные вычисления</font> | ||
| + | '''int'''[] hmax = buildHmax(x, m) | ||
| + | '''int'''[] kmin = buildKmin(hmax, m) | ||
| + | '''int'''[] rmin = buildRmin(hmax, kmin, m) | ||
| + | |||
| + | <font color="green">// Построение массива <tex>h</tex></font> | ||
| + | '''int''' h[m] | ||
| + | '''int''' s = -1 | ||
| + | '''int''' r = m | ||
| + | '''for''' i = 0 .. m - 1 | ||
| + | '''if''' kmin[i] == 0 | ||
| + | h[--r] = i | ||
| + | '''else''' | ||
| + | h[++s] = i | ||
| + | '''int''' nd = s | ||
| + | |||
| + | '''int'''[] shift = buildShift(kmin, rmin, h, nd, m) | ||
| + | '''int'''[] next = buildNext(kmin, rmin, h, nd, m) | ||
| + | |||
| + | <font color="green">// Поиск подстроки</font> | ||
| + | '''int''' i = 0 | ||
| + | '''int''' j = 0 | ||
| + | '''int''' last = -1 | ||
| + | '''while''' j <= n - m | ||
| + | '''while''' i < m '''and''' last < j + h[i] '''and''' x[h[i]] == y[j + h[i]] | ||
| + | ++i | ||
| + | '''if''' i >= m '''or''' last >= j + h[i] | ||
| + | OUTPUT(j) | ||
| + | i = m | ||
| + | '''if''' i > nd | ||
| + | last = j + m - 1 | ||
| + | j += shift[i] | ||
| + | i = next[i] | ||
==Асимптотики== | ==Асимптотики== | ||
Версия 20:40, 13 июня 2014
Алгоритм, разработанный Ливио Колусси, профессором итальянского университета Padova, и опубликованный им в 1991 году, является продолжением работы над оптимизацией алгоритма Кнута-Морриса-Пратта. Предназначен для поиска одной подстроки в одном тексте.
Содержание
Алгоритм
Алгоритм сравнивает символы шаблона один за другим с символами исходной строки . Для сдвигов шаблона относительно исходной строки применяются вспомогательные функции, описанные ниже.
Обозначим за — префикс-функцию, но при этом она определена для и имеет значение по умолчанию.
Отметим, что нумерация символов строк и элементов массива у нас начинается с .
Множество всех позиций шаблона разделим на два (дизъюнктных) непересекающихся множества. Тогда каждая попытка сравнения шаблона с исходной строкой после очередного сдвига состоит из двух случаев.
| Определение: |
| В первом случае сравнения выполняются слева направо с символами текста, выровненными с шаблоном в позиции, для которой значение функции строго больше . Такие позиции будем называть насыщенными (noholes). |
| Определение: |
| Во втором случае будут производиться сравнения в оставшихся позициях справа налево. Такие позиции будем называть ненасыщенными (holes). |
Такая стратегия предоставляет, как минимум, 2 преимущества:
- когда несовпадение появляется во время первого случая, после соответствующего сдвига уже нет необходимости делать проверки в насыщенных позициях, которые были проверены на предыдущем шаге.
- когда несовпадение появляется во время второго случая, это означает, что суффикс шаблона совпал с подстрокой исходной строки и после соответствующего сдвига префикс шаблона будет все ещё совпадать с этой подстрокой, поэтому нет необходимости в повторной проверке.
| Определение: |
| Обозначим за . Функция определена для всех позиций , у которых . |
Если , то периодичность шаблона заканчивается в позиции .
Очевидно, что для позиция :
- насыщенная, если ,
- ненасыщенная, в остальных случаях.
Обозначим за количество насыщенных позиций в шаблоне .
Массив содержит первыми элементами насыщенных позиций в возрастающем порядке и затем ненасыщенных в убывающем порядке, т.е.
- для всех насыщенная позиция и для .
- для всех ненасыщенная и для .
| Определение: |
| Обозначим за наименьший период шаблона большего, чем . Функция определена для всех позиций , у которых . |
| Определение: |
| Обозначим за наименьший число такое, что . |
Теперь рассмотрим 2 случаях, возможных при очередной попытке сравнения шаблона с подстрокой из текста. Допустим, что шаблон выровнен с подстрокой .
Первая случай
Рассмотрим случай, когда для и .
Пусть .
Тогда нет вхождений шаблона , начиная с и может быть сдвинут на позиций вправо.
Кроме того равенство для всех означает, что сравнения могут продолжены с символов и .
Второй случай
Теперь рассмотрим ситуацию, когда для и для .
Пусть позиций вправо.
Тогда нет вхождений шаблона , начиная с и может быть сдвинут на .
Кроме того равенство означает, что сравнения могут продолжены с символов и .
Предварительные вычисления
Для вычисления значений будем использовать вспомогательную функцию .
| Определение: |
Обозначим за функцию, для которой выполняется:
|
| Определение: |
| Обозначим за количество насыщенных позиций строго меньших . |
Теперь мы можем определить два функции и :
- и для всех ;
- и для всех ;
- и .
Таким образом, при возникновении несовпадения между и окно сравнения должно быть сдвинуто на и сравнения могут быть продолжены с позиции шаблона .
Псевдокод
Функция для построения массива . Наивный вариант
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)
// Вычисление массива
int nhd0[m]
int s = 0
for i = 0 .. m - 1
nhd0[i] = s
if kmin[i] > 0
++s
// Вычисление массива
int next[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
Основная функция алгоритма Колусси.
void COLUSSI(char[] x, char[] y)
int n = length(y)
int m = length(x)
// Предварительные вычисления
int[] hmax = buildHmax(x, m)
int[] kmin = buildKmin(hmax, m)
int[] rmin = buildRmin(hmax, kmin, m)
// Построение массива
int h[m]
int s = -1
int r = m
for i = 0 .. m - 1
if kmin[i] == 0
h[--r] = i
else
h[++s] = i
int nd = s
int[] shift = buildShift(kmin, rmin, h, nd, m)
int[] next = buildNext(kmin, rmin, h, nd, m)
// Поиск подстроки
int i = 0
int j = 0
int last = -1
while j <= n - m
while i < m and last < j + h[i] and x[h[i]] == y[j + h[i]]
++i
if i >= m or last >= j + h[i]
OUTPUT(j)
i = m
if i > nd
last = j + m - 1
j += shift[i]
i = next[i]
Асимптотики
- Фаза предварительных вычислений занимает времени и памяти;
- В худшем случае поиск требует сравнений.
где — длина исходного текста, — длина шаблона
Сравнение с другими алгоритмами
Достоинства
- Поиск выполняется за в отличие от алгоритма Кнута-Морриса-Пратта, поиск в котором занимается , что помогает уменьшить константу при .
- Фаза предобработки выполняется за в отличие от алгоритма Бойера-Мура, где в наилучшем случае можно получить время , что плохо при больших алфавитах.
Недостатки
- Сложность реализации.
Источники
- COLUSSI L., 1991, Correctness and efficiency of the pattern matching algorithms, Information and Computation 95(2):225-251.
- Colussi algorithm
- Colussi.ppt