Алгоритм Колусси — различия между версиями
Kabanov (обсуждение | вклад) м |
м (rollbackEdits.php mass rollback) |
||
(не показано 15 промежуточных версий 5 участников) | |||
Строка 1: | Строка 1: | ||
+ | Алгоритм, разработанный Ливио Колусси, профессором итальянского университета Padova, и опубликованный им в 1991 году, является продолжением работы над оптимизацией [[Алгоритм Кнута-Морриса-Пратта|алгоритма Кнута-Морриса-Пратта]]. Предназначен для поиска одной подстроки в одном тексте. | ||
+ | |||
==Алгоритм== | ==Алгоритм== | ||
+ | Алгоритм сравнивает символы шаблона <tex>x</tex> один за другим с символами исходной строки <tex>y</tex>. Для сдвигов шаблона относительно исходной строки применяются вспомогательные функции, описанные ниже. | ||
+ | |||
+ | Пусть <tex>|y|=n</tex> и <tex>|x|=m</tex>. | ||
+ | |||
+ | Обозначим за <tex>\mathrm{Kmp}</tex> {{---}} [[Префикс-функция|префикс-функцию]], но при этом она определена для <tex>x[0] \dots x[m-1]</tex> и имеет значение <tex>-1</tex> по умолчанию. | ||
+ | |||
Отметим, что нумерация символов строк и элементов массива у нас начинается с <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> и после соответствующего сдвига префикс шаблона будет все ещё совпадать с этой подстрокой, поэтому нет необходимости в повторной проверке. |
{{Определение | {{Определение | ||
|definition= | |definition= | ||
− | Обозначим за <tex>\mathrm{K_{min}}(i) = \min\{k : \ x[0 \dots i-1-k]=x[d \dots i-1]\ and\ x[i-k] \neq x[i]\}</tex>. Функция <tex>\mathrm{K_{min}}</tex> определена для всех позиций <tex>i</tex>, у которых <tex>Kmp(i) \neq -1</tex>. | + | Обозначим за <tex>\mathrm{K_{min}}(i) = \min\{k : \ x[0 \dots i-1-k]=x[d \dots i-1]\ and\ x[i-k] \neq x[i]\}</tex>. Функция <tex>\mathrm{K_{min}}</tex> определена для всех позиций <tex>i</tex>, у которых <tex>\mathrm{Kmp}(i) \neq -1</tex>. |
}} | }} | ||
Строка 28: | Строка 34: | ||
Очевидно, что для <tex>0 < i < m</tex> позиция <tex>i</tex>: | Очевидно, что для <tex>0 < i < m</tex> позиция <tex>i</tex>: | ||
− | * насыщенная, если <tex>\mathrm{K_{min}}(i-1) \neq 0</tex> | + | * насыщенная, если <tex>\mathrm{K_{min}}(i-1) \neq 0</tex>, |
− | * ненасыщенная, | + | * ненасыщенная, в остальных случаях. |
Обозначим за <tex>nd+1</tex> количество насыщенных позиций в шаблоне <tex>x</tex>. | Обозначим за <tex>nd+1</tex> количество насыщенных позиций в шаблоне <tex>x</tex>. | ||
Строка 39: | Строка 45: | ||
{{Определение | {{Определение | ||
|definition= | |definition= | ||
− | Обозначим за <tex>\mathrm{R_{min}}(i)</tex> наименьший период <tex>r</tex> шаблона <tex>x</tex> большего, чем <tex>i</tex>. Функция <tex>\mathrm{R_{min}}</tex> определена для всех позиций <tex>i</tex>, у которых <tex>Kmp(i) = -1</tex>. | + | Обозначим за <tex>\mathrm{R_{min}}(i)</tex> наименьший период <tex>r</tex> шаблона <tex>x</tex> большего, чем <tex>i</tex>. Функция <tex>\mathrm{R_{min}}</tex> определена для всех позиций <tex>i</tex>, у которых <tex>\mathrm{Kmp}(i) = -1</tex>. |
}} | }} | ||
{{Определение | {{Определение | ||
|definition= | |definition= | ||
− | Обозначим за <tex>\mathrm{first}(u)</tex> | + | Обозначим за <tex>\mathrm{first}(u)</tex> наименьшее число <tex>v</tex> такое, что <tex>u \leqslant h[v]</tex>. |
}} | }} | ||
− | Допустим, что шаблон <tex>x</tex> выровнен с <tex>y[j \dots j+m-1]</tex>. | + | Теперь рассмотрим 2 случая, возможных при очередной попытке сравнения шаблона с подстрокой из текста. Допустим, что шаблон <tex>x</tex> выровнен с подстрокой <tex>y[j \dots j+m-1]</tex>. |
− | == | + | === Первый случай === |
Рассмотрим случай, когда <tex>x[h[k]]=y[j+h[k]]</tex> для <tex>0 \leqslant k < r < nd</tex> и <tex>x[h[r]] \neq y[j+h[r]]</tex>. | Рассмотрим случай, когда <tex>x[h[k]]=y[j+h[k]]</tex> для <tex>0 \leqslant k < r < nd</tex> и <tex>x[h[r]] \neq y[j+h[r]]</tex>. | ||
Строка 56: | Строка 62: | ||
Тогда нет вхождений шаблона <tex>x</tex>, начиная с <tex>y[j \dots j']</tex> и <tex>x</tex> может быть сдвинут на <tex>\mathrm{K_{min}}(h[r])</tex> позиций вправо. | Тогда нет вхождений шаблона <tex>x</tex>, начиная с <tex>y[j \dots j']</tex> и <tex>x</tex> может быть сдвинут на <tex>\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>k : 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>. |
− | == | + | [[Файл:colussi-algorithm-1.png|450px|thumb|center|Несовпадение в насыщенной позиции. Насыщенные позиции обозначены черными кругами и сравниваются слева направо.]] |
+ | |||
+ | === Второй случай === | ||
Теперь рассмотрим ситуацию, когда <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>. | ||
− | Пусть <tex>j' = j + \mathrm{R_{min}}(h[r])</tex> | + | Пусть <tex>j' = j + \mathrm{R_{min}}(h[r])</tex>. |
− | Тогда нет вхождений шаблона <tex>x</tex>, начиная с <tex>y[j \dots j']</tex> и <tex>x</tex> может быть сдвинут на <tex>\mathrm{R_{min}}(h[r])</tex>. | + | Тогда нет вхождений шаблона <tex>x</tex>, начиная с <tex>y[j \dots j']</tex> и <tex>x</tex> может быть сдвинут на <tex>\mathrm{R_{min}}(h[r])</tex> позиций вправо. |
− | Кроме того <tex>x[0 \dots m-1-\mathrm{R_{min}}(h[r])]=y[j' \dots j+m-1]</tex> означает, что сравнения могут продолжены с <tex>x[h[\mathrm{first}(m-1-\mathrm{R_{min}}(h[r]))]]</tex> и <tex>y[j'+h[\mathrm{first}(m-1-\mathrm{R_{min}}(h[r]))]]</tex>. | + | Кроме того равенство <tex>x[0 \dots m-1-\mathrm{R_{min}}(h[r])]=y[j' \dots j+m-1]</tex> означает, что сравнения могут продолжены с символов <tex>x[h[\mathrm{first}(m-1-\mathrm{R_{min}}(h[r]))]]</tex> и <tex>y[j'+h[\mathrm{first}(m-1-\mathrm{R_{min}}(h[r]))]]</tex>. |
− | == Предварительные вычисления == | + | [[Файл:colussi-algorithm-2.png|450px|thumb|center|Несовпадение в ненасыщенной позиции. Ненасыщенные позиции обозначены белыми кругами и сравниваются справа налево.]] |
+ | |||
+ | === Предварительные вычисления === | ||
Для вычисления значений <tex>\mathrm{K_{min}}</tex> будем использовать вспомогательную функцию <tex>\mathrm{H_{max}}</tex>. | Для вычисления значений <tex>\mathrm{K_{min}}</tex> будем использовать вспомогательную функцию <tex>\mathrm{H_{max}}</tex>. | ||
{{Определение | {{Определение | ||
Строка 75: | Строка 85: | ||
* <tex>x[\mathrm{H_{max}}(k)] \neq x[\mathrm{H_{max}}(k)-k]</tex>. | * <tex>x[\mathrm{H_{max}}(k)] \neq x[\mathrm{H_{max}}(k)-k]</tex>. | ||
}} | }} | ||
+ | |||
+ | {{Определение | ||
+ | |definition= | ||
+ | Обозначим за <tex>\mathrm{nhd0}(i)</tex> количество насыщенных позиций строго меньших <tex>i</tex>. | ||
+ | }} | ||
+ | |||
+ | Теперь мы можем определить два функции <tex>shift</tex> и <tex>next</tex>: | ||
+ | * <tex>\mathrm{shift}(i)=\mathrm{K_{min}}(h[i])</tex> и <tex>\mathrm{next}(i)=\mathrm{nhd0}(h[i]-\mathrm{K_{min}}(h[i]))</tex> для всех <tex>i : i < nd</tex>; | ||
+ | * <tex>\mathrm{shift}(i)=\mathrm{R_{min}}(h[i])</tex> и <tex>\mathrm{next}(i)=\mathrm{nhd0}(m-\mathrm{R_{min}}(h[i]))</tex> для всех <tex>i : nd \leqslant i < m</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>\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): | ||
Строка 82: | Строка 106: | ||
'''for''' k = 1 .. m | '''for''' k = 1 .. m | ||
'''int''' i = k | '''int''' i = k | ||
− | '''while''' x[i] == x[i - k] | + | '''while''' i < m and x[i] == x[i - k] |
i++ | i++ | ||
hmax[k] = i | hmax[k] = i | ||
− | return hmax | + | '''return''' hmax |
Явная реализация по определению, очевидно, работает за <tex>O(m^2)</tex> и требует <tex>O(m)</tex> памяти. | Явная реализация по определению, очевидно, работает за <tex>O(m^2)</tex> и требует <tex>O(m)</tex> памяти. | ||
Строка 95: | Строка 119: | ||
'''int''' k = 1 | '''int''' k = 1 | ||
'''while''' k <= m | '''while''' k <= m | ||
− | '''while''' x[i] == x[i - k] | + | '''while''' i < m and x[i] == x[i - k] |
− | i++ | + | i++ |
hmax[k] = i | hmax[k] = i | ||
'''int''' q = k + 1 | '''int''' q = k + 1 | ||
Строка 103: | Строка 127: | ||
q++ | q++ | ||
k = q | k = q | ||
− | if k == i + 1 | + | '''if''' k == i + 1 |
i = k | i = k | ||
'''return''' hmax | '''return''' hmax | ||
Строка 109: | Строка 133: | ||
Функция для построения массива <tex>\mathrm{K_{min}}</tex>. | Функция для построения массива <tex>\mathrm{K_{min}}</tex>. | ||
− | '''int'''[] buildKmin('''int'''[] hmax, '''int''' m) | + | '''int'''[] buildKmin('''int'''[] hmax, '''int''' m): |
'''int''' kmin[m] | '''int''' kmin[m] | ||
'''for''' i = m .. 1 | '''for''' i = m .. 1 | ||
Строка 117: | Строка 141: | ||
Функция для построения массива <tex>\mathrm{R_{min}}</tex>. | Функция для построения массива <tex>\mathrm{R_{min}}</tex>. | ||
− | '''int'''[] buildRmin('''int'''[] hmax, '''int'''[] kmin, '''int''' m) | + | '''int'''[] buildRmin('''int'''[] hmax, '''int'''[] kmin, '''int''' m): |
'''int''' rmin[m] | '''int''' rmin[m] | ||
'''int''' r = 0 | '''int''' r = 0 | ||
Строка 128: | Строка 152: | ||
'''else''' | '''else''' | ||
rmin[i] = 0 | rmin[i] = 0 | ||
− | 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] | ||
'''for''' i = 0 .. nd | '''for''' i = 0 .. nd | ||
Строка 140: | Строка 164: | ||
'''return''' shift | '''return''' shift | ||
− | Функция для построения массива <tex>\mathrm{ | + | Функция для построения массива <tex>\mathrm{next}</tex>. |
+ | '''int'''[] buildNext('''int'''[] kmin, '''int'''[] rmin, '''int'''[] h, '''int''' nd, '''int''' m): | ||
+ | <font color="green">// Вычисление массива <tex>\mathrm{nhd0}</tex></font> | ||
+ | '''int''' nhd0[m] | ||
+ | '''int''' s = 0 | ||
+ | '''for''' i = 0 .. m - 1 | ||
+ | nhd0[i] = s | ||
+ | if kmin[i] > 0 | ||
+ | ++s | ||
+ | |||
+ | <font color="green">// Вычисление массива <tex>\mathrm{next}</tex></font> | ||
+ | '''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 | ||
+ | Основная функция алгоритма Колусси. | ||
+ | '''function''' 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] | ||
==Асимптотики== | ==Асимптотики== | ||
− | * | + | * Фаза предварительных вычислений занимает <tex>O(m)</tex> времени и памяти; |
− | + | * В худшем случае поиск требует <tex>O(n)</tex> сравнений. | |
− | * | + | |
− | + | где <tex>n</tex> {{---}} длина исходного текста, <tex>m</tex> {{---}} длина шаблона | |
==Сравнение с другими алгоритмами== | ==Сравнение с другими алгоритмами== | ||
Строка 156: | Строка 235: | ||
===Недостатки=== | ===Недостатки=== | ||
* Сложность реализации. | * Сложность реализации. | ||
− | ==Источники== | + | |
+ | ==Источники информации== | ||
* COLUSSI L., 1991, Correctness and efficiency of the pattern matching algorithms, Information and Computation 95(2):225-251. | * COLUSSI L., 1991, Correctness and efficiency of the pattern matching algorithms, Information and Computation 95(2):225-251. | ||
* [http://www-igm.univ-mlv.fr/~lecroq/string/node10.html Colussi algorithm] | * [http://www-igm.univ-mlv.fr/~lecroq/string/node10.html Colussi algorithm] | ||
Строка 163: | Строка 243: | ||
[[Категория: Дискретная математика и алгоритмы]] | [[Категория: Дискретная математика и алгоритмы]] | ||
[[Категория: Поиск подстроки в строке]] | [[Категория: Поиск подстроки в строке]] | ||
+ | [[Категория: Точный поиск]] |
Текущая версия на 19:42, 4 сентября 2022
Алгоритм, разработанный Ливио Колусси, профессором итальянского университета Padova, и опубликованный им в 1991 году, является продолжением работы над оптимизацией алгоритма Кнута-Морриса-Пратта. Предназначен для поиска одной подстроки в одном тексте.
Алгоритм
Алгоритм сравнивает символы шаблона
один за другим с символами исходной строки . Для сдвигов шаблона относительно исходной строки применяются вспомогательные функции, описанные ниже.Пусть
и .Обозначим за префикс-функцию, но при этом она определена для и имеет значение по умолчанию.
—Отметим, что нумерация символов строк и элементов массива у нас начинается с
.Множество всех позиций шаблона разделим на два дизъюнктных множества. Тогда каждая попытка сравнения шаблона с исходной строкой после очередного сдвига состоит из двух случаев.
Определение: |
В первом случае сравнения выполняются слева направо с символами текста, выровненными с шаблоном в позиции, для которой значение функции | строго больше . Такие позиции будем называть насыщенными (англ. noholes).
Определение: |
Во втором случае будут производиться сравнения в оставшихся позициях справа налево. Такие позиции будем называть ненасыщенными (англ. holes). |
Такая стратегия предоставляет, как минимум, 2 преимущества:
- когда несовпадение появляется во время первого случая, после соответствующего сдвига уже нет необходимости делать проверки в насыщенных позициях, которые были проверены на предыдущем шаге.
- когда несовпадение появляется во время второго случая, это означает, что суффикс шаблона совпал с подстрокой исходной строки и после соответствующего сдвига префикс шаблона будет все ещё совпадать с этой подстрокой, поэтому нет необходимости в повторной проверке.
Определение: |
Обозначим за | . Функция определена для всех позиций , у которых .
Если , то периодичность шаблона заканчивается в позиции .
Очевидно, что для
позиция :- насыщенная, если ,
- ненасыщенная, в остальных случаях.
Обозначим за
количество насыщенных позиций в шаблоне .Массив
содержит первыми элементами насыщенных позиций в возрастающем порядке и затем ненасыщенных в убывающем порядке, т.е.- для всех насыщенная позиция и для .
- для всех ненасыщенная и для .
Определение: |
Обозначим за | наименьший период шаблона большего, чем . Функция определена для всех позиций , у которых .
Определение: |
Обозначим за | наименьшее число такое, что .
Теперь рассмотрим 2 случая, возможных при очередной попытке сравнения шаблона с подстрокой из текста. Допустим, что шаблон выровнен с подстрокой .
Первый случай
Рассмотрим случай, когда
для и .Пусть
.Тогда нет вхождений шаблона
, начиная с и может быть сдвинут на позиций вправо.Кроме того равенство
для всех означает, что сравнения могут продолжены с символов и .Второй случай
Теперь рассмотрим ситуацию, когда
для и для .Пусть
.Тогда нет вхождений шаблона
, начиная с и может быть сдвинут на позиций вправо.Кроме того равенство
означает, что сравнения могут продолжены с символов и .Предварительные вычисления
Для вычисления значений
будем использовать вспомогательную функцию .Определение: |
Обозначим за
| функцию, для которой выполняется:
Определение: |
Обозначим за | количество насыщенных позиций строго меньших .
Теперь мы можем определить два функции и :
- и для всех ;
- и для всех ;
- и .
Таким образом, при возникновении несовпадения между
и окно сравнения должно быть сдвинуто на и сравнения могут быть продолжены с позиции шаблона .Псевдокод
Функция для построения массива
.Наивный вариант
int[] buildHmax(char[] x, int m): int hmax[m + 1] for k = 1 .. m int i = k while i < m and 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 i < m and 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
Основная функция алгоритма Колусси.
function 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