SlideShare a Scribd company logo
1 of 25
Основные алгоритмические
стратегии (продолжение)

7. Алгоритмы работы со
строками
Такие задачи, как сжатие информации, поиск в
базе данных, анализ ДНК, поиск документов в
Интернете, поиск файлов и папок на
компьютере, поиск и замена символов в
документе текстового процессора объединяет
необходимость использования алгоритмов
поиска подстрок.
Формальная постановка задачи поиска подстрок.
Предполагается, что задан текст в виде массива
T[1..n] длины n и образец – в виде массива
P[l..m] длины m ≤ n. Далее, предполагается, что
элементы массивов P и T – символы из
конечного алфавита Σ. Например, алфавит
может иметь вид Σ = {0, 1} или Σ = {a, b, ..., z}.
Символы массивов P и T часто называют
строками или символами.
Говорят, что образец P встречается в тексте T со
сдвигом s, если 0 ≤ s ≤ n – m и T[s + l..s + m] =
P[l..m] (другими словами, 1 ≤ j ≤ m T[s + j] = P[j]).
Можно также сказать, что образец P
встречается в тексте T, начиная с позиции s + 1.
Если образец P встречается в тексте T со
сдвигом s, то величину s называют допустимым
сдвигом, в противном случае ее называют
недопустимым сдвигом. Задача поиска подстрок
– это задача поиска всех допустимых сдвигов, с
которыми заданный образец P встречается в
тексте T.
Пример: пусть имеются образец P = abaa и текст
T = abcabaabcabac. Тогда образец встречается в
тексте только один раз, со сдвигом s = 3.
Говорят, что сдвиг s = 3 является допустимым.
Эффективные алгоритмы поиска подстрок
производят определенную
предварительную обработку
представленного образца, а затем
находят все допустимые сдвиги.
Последняя фаза при этом будет
называться поиском.
В табл. 1 приведены времена
предварительной обработки и сравнения
для некоторых алгоритмов. Полное время
работы каждого алгоритма равно сумме
времени предварительной обработки и
времени сравнения.
Таблица 1
Время работы алгоритмов поиска подстрок
Алгоритм

Время предварительной
обработки

Время сравнения

Простейший

0

O((n – m + 1)m)

Рабина-Карпа

Θ(m)

O((n – m + 1)m)

Конечный автомат

Θ(m|Σ|)

Θ(n)

Кнута-Морриса-Пратта

Θ(m)

Θ(n)
Обозначения и термины.
 Σ* множество всех строк конечной длины,
образованных с помощью символов
алфавита Σ;
 пустая строка конечной длины, которая
обозначается ε, также принадлежит
множеству Σ*;
 длина строки x обозначается |x|;
 конкатенация двух строк x и y, которая
обозначается xy, имеет длину |x| + |y| и
состоит из символов строки x, после
которых следуют символы строки y.
Говорят, что строка w – префикс строки x
(обозначается w ⊂ x), если существует такая
строка y ∈ Σ*, что x = wy. Заметим, что если w ⊂
x, то |w| ≤ |x|. Аналогично, строку w называют
суффиксом строки x (обозначается как w ⊃ x),
если существует такая строка y ∈ Σ*, что x = yw.
Из соотношения w ⊃ x также следует
неравенство |w| ≤ |x|. Пустая строка ε является
одновременно и суффиксом, и префиксом любой
строки.
Примеры префикса и суффикса: ab ⊂ abcca и сса ⊃
abcca.
Следует иметь в виду, что для произвольных
строк x и y и для любого символа a соотношение
x ⊃ y выполняется тогда и только тогда, когда
xa ⊃ ya.
Отношения ⊂ и ⊃ являются транзитивными.
Лемма 1 (Лемма о перекрывающихся
суффиксах). Предположим, что x, y и z –
строки, для которых выполняются
соотношения x ⊃ z и y ⊃ z. Если
|x| ≤ |y|, то
x ⊃ y. Если |x| ≥ |y|, то y ⊃ x. Если |x| = |y|, то x
= y.
Обозначим для краткости k-символьный
префикс P[l..k] образца Р[l..m] через Pk. Таким
образом, P0 = ε и Рm = Р = Р[l..m]. Аналогично,
k-символьный префикс текста T обозначим Tk.
С помощью этих обозначений задачу поиска
подстрок можно сформулировать как задачу о
выявлении всех сдвигов s в интервале 0 ≤ s ≤
n – m, таких что Р ⊃ Ts+m.
Будем предполагать, что в псевдокоде сравнение двух
строк одинаковой длины является примитивной
операцией. Если строки сравниваются слева направо,
и процесс сравнения прерывается, когда обнаружено
несовпадение, то считается, что время, которое
требуется для подобного теста, выражается линейной
функцией от количества совпавших символов. Точнее
говоря, считается, что тест "х = у" выполняется за
время Θ(t + 1), где t – длина самой длинной строки z,
такой, что z ⊂ x и z ⊂ у.
Θ(t + 1) вместо Θ(t) мы
пишем, чтобы учесть случай, когда t = 0 (в этой
ситуации не совпадает первый же сравниваемый
символ, но для проверки этого требуется некоторое
положительное время).
В простейшем алгоритме поиск всех допустимых сдвигов
производится с помощью цикла, в котором
проверяется условие Р[1..m] = T[s + 1..s + m] для
каждого из n – m + 1 возможных значений s.
Naive_String_Matcher(T, Р)
1. n ← length[T]
2. m ← length[P]
3. for s ← 0 to n – m
4. do if P[l.. m] = T[з + 1.. s + m]
5. then print "Образец обнаружен при
сдвиге" s
Время работы процедуры
Naive_String_Matcher равно Θ((n – m
+1)m) и эта оценка достаточно точна в
наихудшем случае.
Рабин и Карп предложили алгоритм поиска
подстрок, показывающий на практике
хорошую производительность, а также
допускающий обобщения на другие
родственные задачи, такие как задача о
сопоставлении двумерного образца. В
алгоритме Рабина-Карпа время Θ(m)
затрачивается на предварительную
обработку, а время его работы в
наихудшем случае равно Θ((n – m +1)m).
Однако с учетом определенных
предположений удается показать, что
среднее время работы этого алгоритма
оказывается существенно лучше.
В этом алгоритме используются обозначения из
элементарной теории чисел, такие как эквивалентность
двух чисел по модулю третьего числа. Для простоты
предположим, что Σ = {0, 1, ..., 9}, т.е. каждый символ – это
десятичная цифра (в общем случае можно предположить,
что каждый символ – это цифра в системе счисления с
основанием d, где d = |Σ|). После этого строку из k
последовательных символов можно рассматривать как
число длиной k. Таким образом, символьная строка 31415
соответствует числу 31415.
Для заданного образца Р[l..m] обозначим через p
соответствующее ему десятичное значение. Аналогично,
для заданного текста T[1..n] обозначим через ts десятичное
значение подстроки Т[s + l..s + m] длиной m при s = 0,1,..., n
– m. Очевидно, что ts = р тогда и только тогда, когда Т[s +
l..s + m] = Р[1..m]. Таким образом, s – допустимый сдвиг
тогда и только тогда, когда ts = р. Если бы значение р
можно было вычислить за время Θ(m), а все значения ts –
за суммарное время Θ(n – m +1), то значения всех
допустимых сдвигов можно было бы определить за время
Θ(m) + Θ(n – m +1) = Θ(n) путем сравнения значения p с
каждым из значений ts.
С помощью правила Горнера величину p можно
вычислить за время Θ(m): p = P[m] + 10 (P[m –
1] + 10 P[m – 2] + … + 10 (P[2] + 10P[1]) … )).
Значение t0 можно вычислить из массива T[1..m]
за время Θ(m) аналогичным способом. Чтобы
вычислить остальные значения t1, t2, …, tn – m за
время Θ(n – m), достаточно заметить, что
величину ts+1 можно вычислить из величины ts за
фиксированное время, так как
ts + 1 = 10(ts – l0m – l T[s + 1]) + T[s + m + 1]).
(1)
Например, если m = 5 и ts = 31415, то нужно
удалить цифру в старшем разряде T[s + 1] = 3
и добавить новую цифру в младший разряд
(предположим, это цифра T[s + 5 + 1] = 2). В
результате мы получаем
t = 10(31415 – 10000 • 3) + 2 = 14152.
Чтобы удалить из числа ts цифру старшего
разряда, из него вычитается значение 10m–1T[s +
+1]; путем умножения на 10 число сдвигается
на одну позицию влево, а в результате
добавления элемента T[s + m + 1] в его
младшем разряде появляется нужная цифра.
Это можно сделать за время Θ(m).
Таким образом, число p можно вычислить за
время Θ(m), величины
t0, t1, t2, …, tn – m за время
Θ(n – m + 1), а все вхождения образца Р[1..m] в
текст T[1..n] можно найти, затратив на фазу
предварительной обработки время в Θ(m), а на
фазу сравнения – время Θ(n – m +1).
Сложность, возникающая в этой процедуре,
может быть связана с тем, что значения p и ts
могут оказаться слишком большими и с ними
будет неудобно работать.
Однако эта проблема имеет простое решение: вычислять
значения p и ts по модулю некоторого числа q. В этом
случае величина p по модулю q вычисляется за время
Θ(m), а вычисление всех величин ts по модулю q – за
время Θ(n – m + 1). В качестве модуля q обычно
выбирается такое простое число, для которого длина
величины 10q не превышает длины машинного слова.
В общем случае, если имеется d-символьный алфавит
{0,1,..., d – 1}, значение q выбирается таким образом,
чтобы длина величины dq не превышала длины
машинного слова, и чтобы с рекуррентным
соотношением (1) было удобно работать по модулю q.
После этого рассматриваемое соотношение принимает
вид
ts + 1 = (d(ts – T[s + 1]h) + T[s + m + 1])) mod q,
(2)
где h ≡ dm – 1 (mod q) – значение, которое приобретает цифра
«1», помещенная в старший разряд m – текстового
окна.
Недостатки: из ts = р(mod q) не следует, что ts = р. С
другой стороны, если ts ≠ р(mod q), то обязательно
выполняется соотношение ts ≠ р и можно сделать
вывод, что сдвиг s недопустимый. Таким образом,
соотношение ts = р(mod q) можно использовать в
качестве быстрого эвристического теста,
позволяющего исключить недопустимые сдвиги s.
Все сдвиги, для которых оно справедливо
необходимо подвергнуть дополнительному
тестированию, чтобы проверить, что действительно
ли сдвиг s является допустимым, или это просто
ложное совпадение. Такое тестирование можно
осуществить путем явной проверки условия Р[l..m] =
T[s + l…s + m]. Если значение q достаточно
большое, то можно надеяться, что ложные
совпадения встречаются довольно редко и
стоимость дополнительной проверки окажется
низкой.
Сформулированная ниже процедура поясняет описанные выше
идеи. В роли входных значений для нее выступает текст Т,
образец Р, разряд d (в качестве значения которого обычно
выбирается |Σ|) и простое число q.
RabinJKarp_Matcher(T, Р, d, q)
1. n ← length[T]
2. m ← length[P]
3. h ← dm – 1 mod q
4. p ← 0
5. t0 ← 0
6. for i ← 1 to m //Предварительная обработка
7. do p ← (dp + P[i]) mod q
8.
t0 ← (d t0 + T[i]) mod q
9. for s ← 0 to n – m //Проверка
10.
do if p = ts
11.
then if P[l.. m] = T[s + 1.. s + m]
12.
then print "Образец обнаружен при сдвиге"
s
13. if s < n – m
14.
then ts + 1← (d(ts – T[s + 1]h) + T[s + m + 1])) mod q
Во многих приложениях ожидается
небольшое количество допустимых
сдвигов (возможно, выражающееся
некоторой константой c). В таких
приложениях математическое ожидание
времени работы алгоритма равно сумме
величины O((n – m +1) + cm) = O(n + m) и
времени, необходимого для обработки
ложных совпадений.
В основу эвристического анализа можно
положить предположение, что приведение
значений по модулю q действует как
случайное отображение множества Σ* на
множество Zq.
В таком случае можно ожидать, что число ложных
совпадений равно O(n/q), потому что вероятность того,
что произвольное число ts будет эквивалентно р по
модулю q, можно оценить как l/q. Поскольку имеется
всего O(n) позиций, в которых проверка в строке 10 дает
отрицательный результат, а на обработку каждого
совпадения затрачивается время O(m), математическое
ожидание времени сравнения в алгоритме РабинаКарпа равно
O(n) + O(m(v + n/q)),
где v – количество допустимых сдвигов. Если v = O(1), a q
выбрано так, что q ≥ m, то приведенное выше время
выполнения равно O(n). Другими словами, если
математическое ожидание количества допустимых
сдвигов мало (O(1)), а выбранное простое число q
превышает длину образца, то можно ожидать, что для
выполнения фазы сравнения процедуре Рабина-Карпа
потребуется время O(n + m). Поскольку m ≤ n, то
математическое ожидание времени сравнения равно
O(n).
Представим алгоритм сравнения строк, который
был предложен Кнутом, Моррисом и Праттом, и
время работы которого линейно зависит от
объема входных данных. В этом алгоритме
используется вспомогательная функции π[1..m],
которая вычисляется по заданному образцу за
время Θ(m). Благодаря этому время сравнения
в этом алгоритме равно Θ(n). Префиксная
функция π, предназначенная для какого-нибудь
образца, инкапсулирует сведения о том, в какой
мере образец совпадает сам с собой после
сдвигов. Эта информация позволяет избежать
ненужных проверок. Поэтому в общем случае
полезно знать ответ на следующий вопрос.
Пусть символы Р[1..q] образца совпадают с
символами текста T[s + l..s + q]. Чему равен
наименьший сдвиг s’ > s, такой что
P[l..k] = T[s' + l..s' + k],
(3)
где s' + к = s + q?
Такой сдвиг s' – первый после сдвига s,
недопустимость которого не обязательно
следует из наших знаний о подстроке T[s + l..s +
q]. B лучшем случае s' = s + q, и все сдвиги s + 1,
s + 2, ..., s + q – 1 немедленно отбрасываются. В
любом случае для нового сдвига s’ нет
необходимости сравнивать первые k символов
образца P и соответствующие символы текста T,
поскольку их совпадение гарантированно
благодаря уравнению (3).
Необходимую информацию об образце можно
получить, сдвигая его вдоль самого себя.
Поскольку T[s' + l..s' + k] – известная часть
текста, она является суффиксом строки Pq.
Поэтому уравнение (3) можно рассматривать
как запрос о максимальном значении k < q,
таком, что Pk ⊃ Pq. Тогда следующий
потенциально допустимый сдвиг равен s' = s +
(q – к). Оказывается, что удобнее хранить
количество k совпадающих при новом сдвиге s'
символов, чем, например, величину s' – s.
Теперь дадим формальное определение.
Префиксной функцией заданного образца
P[1..m] называется функция π:
{1, 2, ..., m} → {0,1,..., m – 1}, такая что
π[q] = max{k : k < q и Pk ⊃ Pq} .
Другими словами, π[q] равно длине
наибольшего префикса образца P,
который является истинным суффиксом
строки Pq. В качестве примера приведем
полную префиксная функция π для
образца ababababca (рис. 1).
i

1

2

3

4

5

6

7

8

9

10

P[i]

a

b

a

b

a

b

a

b

c

a

π[i]

0

0

1

2

3

4

5

6

0

1

Рис. 1. Полная префиксная функция для образца ababababca
Алгоритм поиска подстрок Кнута-Морриса-Пратта приведен
ниже в виде псевдокода процедуры KMP_Matcher. В
процедуре KMP_Matcher вызывается вспомогательная
процедура Compute_Prefix_Function, в которой
вычисляется функция π.
KMP_Matcher(T, P)
1. n ← length[T]
2. m ← length[P]
3. π ← Compute_Prefix_Function(P)
4. q ← 0 //Число совпавших символов
5. for i ← 1 to n //Сканирование текста слева направо
6. do while q > 0 и P[q + 1] ≠ T[i]
7.
do q ← π[q] //Следующий символ не совпадает
8.
if P[q + 1] = T[i]
9.
then q ← q + 1 //Следующий символ совпадает
10.
if q = m //Совпали ли все символы образца P?
11.
then print "Образец обнаружен при сдвиге" i–m
12.
q ← π[q] //Поиск следующего совпадения
Compute_Prefix_Function(P)
1. m ← length[P]
2. π[1] ← 0
3. k ← 0
4. for q ← 2 to m
5. do while k > 0 и P[k + 1] ≠ P[q]
6.
do k ← π[k]
7.
if P[k + 1] = P[q]
8.
then k ← k + 1
9.
π[q] ← k
10. return π
Можно формально показать, что процедура
Compute_Prefix_Function выполняется за время Θ(m), а
время выполнения сравнений в процедуре KMP_Matcher
равно Θ(n). Также можно формально доказать
корректность приведенных процедур, с помощью
которых реализуется алгоритм Кнута-Морриса-Пратта.

More Related Content

What's hot

ЗАМЕЧАНИЕ К ПОСТРОЕНИЮ НЕ ИМЕЮЩЕГО РИСКА ПОРТФЕЛЯ
ЗАМЕЧАНИЕ К ПОСТРОЕНИЮ НЕ ИМЕЮЩЕГО РИСКА ПОРТФЕЛЯ ЗАМЕЧАНИЕ К ПОСТРОЕНИЮ НЕ ИМЕЮЩЕГО РИСКА ПОРТФЕЛЯ
ЗАМЕЧАНИЕ К ПОСТРОЕНИЮ НЕ ИМЕЮЩЕГО РИСКА ПОРТФЕЛЯ Ilya Gikhman
 
Скорость роста функций
Скорость роста функцийСкорость роста функций
Скорость роста функцийDEVTYPE
 
многочлены чебышева
многочлены чебышевамногочлены чебышева
многочлены чебышеваVladimir Kukharenko
 
Лекция 5. Метод конечных разностей (параллельные алгоритмы в стандарте MPI)
Лекция 5. Метод конечных разностей (параллельные алгоритмы в стандарте MPI)Лекция 5. Метод конечных разностей (параллельные алгоритмы в стандарте MPI)
Лекция 5. Метод конечных разностей (параллельные алгоритмы в стандарте MPI)Alexey Paznikov
 
о построении цены производных инструментов
о построении цены производных инструментово построении цены производных инструментов
о построении цены производных инструментовIlya Gikhman
 
Основы комбинаторики II. Разбор задач
Основы комбинаторики II. Разбор задачОсновы комбинаторики II. Разбор задач
Основы комбинаторики II. Разбор задачDEVTYPE
 
Исследование производной
Исследование производнойИсследование производной
Исследование производнойagafonovalv
 
Математическое дополнение
Математическое дополнениеМатематическое дополнение
Математическое дополнениеBigVilly
 
20110306 systems of_typed_lambda_calculi_moskvin_lecture04
20110306 systems of_typed_lambda_calculi_moskvin_lecture0420110306 systems of_typed_lambda_calculi_moskvin_lecture04
20110306 systems of_typed_lambda_calculi_moskvin_lecture04Computer Science Club
 
Лекция №14. Графы: кратчайшие пути и максимальные потоки. Предмет "Структуры ...
Лекция №14. Графы: кратчайшие пути и максимальные потоки. Предмет "Структуры ...Лекция №14. Графы: кратчайшие пути и максимальные потоки. Предмет "Структуры ...
Лекция №14. Графы: кратчайшие пути и максимальные потоки. Предмет "Структуры ...Nikolay Grebenshikov
 
предел последовательности
предел последовательностипредел последовательности
предел последовательностиtomik1044
 
20110224 systems of_typed_lambda_calculi_moskvin_lecture03
20110224 systems of_typed_lambda_calculi_moskvin_lecture0320110224 systems of_typed_lambda_calculi_moskvin_lecture03
20110224 systems of_typed_lambda_calculi_moskvin_lecture03Computer Science Club
 
Лекция №2. Алгоритмические проблемы. Стандартные схемы программ. Предмет "Тео...
Лекция №2. Алгоритмические проблемы. Стандартные схемы программ. Предмет "Тео...Лекция №2. Алгоритмические проблемы. Стандартные схемы программ. Предмет "Тео...
Лекция №2. Алгоритмические проблемы. Стандартные схемы программ. Предмет "Тео...Nikolay Grebenshikov
 
Линейная алгебра - II
Линейная алгебра - IIЛинейная алгебра - II
Линейная алгебра - IIDEVTYPE
 
Основы теории графов 11: гамильтоновы циклы
Основы теории графов 11: гамильтоновы циклыОсновы теории графов 11: гамильтоновы циклы
Основы теории графов 11: гамильтоновы циклыAlex Dainiak
 
слайды к лаб1 тмм
слайды к лаб1 тммслайды к лаб1 тмм
слайды к лаб1 тммstudent_kai
 

What's hot (20)

ЗАМЕЧАНИЕ К ПОСТРОЕНИЮ НЕ ИМЕЮЩЕГО РИСКА ПОРТФЕЛЯ
ЗАМЕЧАНИЕ К ПОСТРОЕНИЮ НЕ ИМЕЮЩЕГО РИСКА ПОРТФЕЛЯ ЗАМЕЧАНИЕ К ПОСТРОЕНИЮ НЕ ИМЕЮЩЕГО РИСКА ПОРТФЕЛЯ
ЗАМЕЧАНИЕ К ПОСТРОЕНИЮ НЕ ИМЕЮЩЕГО РИСКА ПОРТФЕЛЯ
 
Скорость роста функций
Скорость роста функцийСкорость роста функций
Скорость роста функций
 
многочлены чебышева
многочлены чебышевамногочлены чебышева
многочлены чебышева
 
Lection10
Lection10Lection10
Lection10
 
лекция 14
лекция 14лекция 14
лекция 14
 
Лекция 5. Метод конечных разностей (параллельные алгоритмы в стандарте MPI)
Лекция 5. Метод конечных разностей (параллельные алгоритмы в стандарте MPI)Лекция 5. Метод конечных разностей (параллельные алгоритмы в стандарте MPI)
Лекция 5. Метод конечных разностей (параллельные алгоритмы в стандарте MPI)
 
о построении цены производных инструментов
о построении цены производных инструментово построении цены производных инструментов
о построении цены производных инструментов
 
Основы комбинаторики II. Разбор задач
Основы комбинаторики II. Разбор задачОсновы комбинаторики II. Разбор задач
Основы комбинаторики II. Разбор задач
 
Исследование производной
Исследование производнойИсследование производной
Исследование производной
 
Математическое дополнение
Математическое дополнениеМатематическое дополнение
Математическое дополнение
 
20110306 systems of_typed_lambda_calculi_moskvin_lecture04
20110306 systems of_typed_lambda_calculi_moskvin_lecture0420110306 systems of_typed_lambda_calculi_moskvin_lecture04
20110306 systems of_typed_lambda_calculi_moskvin_lecture04
 
Лекция №14. Графы: кратчайшие пути и максимальные потоки. Предмет "Структуры ...
Лекция №14. Графы: кратчайшие пути и максимальные потоки. Предмет "Структуры ...Лекция №14. Графы: кратчайшие пути и максимальные потоки. Предмет "Структуры ...
Лекция №14. Графы: кратчайшие пути и максимальные потоки. Предмет "Структуры ...
 
предел последовательности
предел последовательностипредел последовательности
предел последовательности
 
5
55
5
 
20110224 systems of_typed_lambda_calculi_moskvin_lecture03
20110224 systems of_typed_lambda_calculi_moskvin_lecture0320110224 systems of_typed_lambda_calculi_moskvin_lecture03
20110224 systems of_typed_lambda_calculi_moskvin_lecture03
 
Proizvodnaja
ProizvodnajaProizvodnaja
Proizvodnaja
 
Лекция №2. Алгоритмические проблемы. Стандартные схемы программ. Предмет "Тео...
Лекция №2. Алгоритмические проблемы. Стандартные схемы программ. Предмет "Тео...Лекция №2. Алгоритмические проблемы. Стандартные схемы программ. Предмет "Тео...
Лекция №2. Алгоритмические проблемы. Стандартные схемы программ. Предмет "Тео...
 
Линейная алгебра - II
Линейная алгебра - IIЛинейная алгебра - II
Линейная алгебра - II
 
Основы теории графов 11: гамильтоновы циклы
Основы теории графов 11: гамильтоновы циклыОсновы теории графов 11: гамильтоновы циклы
Основы теории графов 11: гамильтоновы циклы
 
слайды к лаб1 тмм
слайды к лаб1 тммслайды к лаб1 тмм
слайды к лаб1 тмм
 

Similar to лекция 10

Лекция №16. Поиск подстрок. Предмет "Структуры и алгоритмы обработки данных"
Лекция №16. Поиск подстрок. Предмет "Структуры и алгоритмы обработки данных"Лекция №16. Поиск подстрок. Предмет "Структуры и алгоритмы обработки данных"
Лекция №16. Поиск подстрок. Предмет "Структуры и алгоритмы обработки данных"Nikolay Grebenshikov
 
Лекция 1: Введение в алгоритмы
Лекция 1: Введение в алгоритмыЛекция 1: Введение в алгоритмы
Лекция 1: Введение в алгоритмыMikhail Kurnosov
 
массивы.строки
массивы.строкимассивы.строки
массивы.строкиdasha2012
 
строковые величины
строковые величиныстроковые величины
строковые величиныmetodkopilka
 
презентация по теме «действительные числа»
презентация по теме «действительные числа»презентация по теме «действительные числа»
презентация по теме «действительные числа»Kirrrr123
 
Линейные коды
Линейные кодыЛинейные коды
Линейные кодыAlex Dainiak
 
функция
функцияфункция
функцияmara2101
 
Функции в языках программирования
Функции в языках программированияФункции в языках программирования
Функции в языках программированияkvlar
 
зависимость между песочной группой графа и его матроидом
зависимость между песочной группой графа и его матроидомзависимость между песочной группой графа и его матроидом
зависимость между песочной группой графа и его матроидомИван Иванов
 
!Predictive analytics part_2
!Predictive analytics part_2!Predictive analytics part_2
!Predictive analytics part_2Vladimir Krylov
 

Similar to лекция 10 (18)

Лекция №16. Поиск подстрок. Предмет "Структуры и алгоритмы обработки данных"
Лекция №16. Поиск подстрок. Предмет "Структуры и алгоритмы обработки данных"Лекция №16. Поиск подстрок. Предмет "Структуры и алгоритмы обработки данных"
Лекция №16. Поиск подстрок. Предмет "Структуры и алгоритмы обработки данных"
 
109130.ppt
109130.ppt109130.ppt
109130.ppt
 
функция
функцияфункция
функция
 
Лекция 1: Введение в алгоритмы
Лекция 1: Введение в алгоритмыЛекция 1: Введение в алгоритмы
Лекция 1: Введение в алгоритмы
 
массивы.строки
массивы.строкимассивы.строки
массивы.строки
 
строковые величины
строковые величиныстроковые величины
строковые величины
 
Основы MATLAB. Численные методы
Основы MATLAB. Численные методыОсновы MATLAB. Численные методы
Основы MATLAB. Численные методы
 
Funkciya 1
Funkciya 1Funkciya 1
Funkciya 1
 
презентация по теме «действительные числа»
презентация по теме «действительные числа»презентация по теме «действительные числа»
презентация по теме «действительные числа»
 
Линейные коды
Линейные кодыЛинейные коды
Линейные коды
 
Unit3
Unit3Unit3
Unit3
 
Unit3
Unit3Unit3
Unit3
 
функция
функцияфункция
функция
 
10
1010
10
 
графы
графыграфы
графы
 
Функции в языках программирования
Функции в языках программированияФункции в языках программирования
Функции в языках программирования
 
зависимость между песочной группой графа и его матроидом
зависимость между песочной группой графа и его матроидомзависимость между песочной группой графа и его матроидом
зависимость между песочной группой графа и его матроидом
 
!Predictive analytics part_2
!Predictive analytics part_2!Predictive analytics part_2
!Predictive analytics part_2
 

More from student_kai

презентация
презентацияпрезентация
презентацияstudent_kai
 
презентации продолжение банкета
презентации продолжение банкетапрезентации продолжение банкета
презентации продолжение банкетаstudent_kai
 
основы программирования на языке C
основы программирования на языке Cосновы программирования на языке C
основы программирования на языке Cstudent_kai
 
презентация курсовой работы
презентация курсовой работыпрезентация курсовой работы
презентация курсовой работыstudent_kai
 
лекция№34
лекция№34лекция№34
лекция№34student_kai
 
лекция№32
лекция№32лекция№32
лекция№32student_kai
 
лекция№33
лекция№33лекция№33
лекция№33student_kai
 
лекция№31
лекция№31лекция№31
лекция№31student_kai
 
лекция№30
лекция№30лекция№30
лекция№30student_kai
 
лекция№29
лекция№29лекция№29
лекция№29student_kai
 
лекция№28
лекция№28лекция№28
лекция№28student_kai
 
лекция№27
лекция№27лекция№27
лекция№27student_kai
 
лекция№26
лекция№26лекция№26
лекция№26student_kai
 
лекция№25
лекция№25лекция№25
лекция№25student_kai
 
лекция№25
лекция№25лекция№25
лекция№25student_kai
 
лекция№24
лекция№24лекция№24
лекция№24student_kai
 
лекция№23
лекция№23лекция№23
лекция№23student_kai
 
лекция№22
лекция№22лекция№22
лекция№22student_kai
 
лекция№21
лекция№21лекция№21
лекция№21student_kai
 
лекция№20
лекция№20лекция№20
лекция№20student_kai
 

More from student_kai (20)

презентация
презентацияпрезентация
презентация
 
презентации продолжение банкета
презентации продолжение банкетапрезентации продолжение банкета
презентации продолжение банкета
 
основы программирования на языке C
основы программирования на языке Cосновы программирования на языке C
основы программирования на языке C
 
презентация курсовой работы
презентация курсовой работыпрезентация курсовой работы
презентация курсовой работы
 
лекция№34
лекция№34лекция№34
лекция№34
 
лекция№32
лекция№32лекция№32
лекция№32
 
лекция№33
лекция№33лекция№33
лекция№33
 
лекция№31
лекция№31лекция№31
лекция№31
 
лекция№30
лекция№30лекция№30
лекция№30
 
лекция№29
лекция№29лекция№29
лекция№29
 
лекция№28
лекция№28лекция№28
лекция№28
 
лекция№27
лекция№27лекция№27
лекция№27
 
лекция№26
лекция№26лекция№26
лекция№26
 
лекция№25
лекция№25лекция№25
лекция№25
 
лекция№25
лекция№25лекция№25
лекция№25
 
лекция№24
лекция№24лекция№24
лекция№24
 
лекция№23
лекция№23лекция№23
лекция№23
 
лекция№22
лекция№22лекция№22
лекция№22
 
лекция№21
лекция№21лекция№21
лекция№21
 
лекция№20
лекция№20лекция№20
лекция№20
 

лекция 10

  • 2. Такие задачи, как сжатие информации, поиск в базе данных, анализ ДНК, поиск документов в Интернете, поиск файлов и папок на компьютере, поиск и замена символов в документе текстового процессора объединяет необходимость использования алгоритмов поиска подстрок. Формальная постановка задачи поиска подстрок. Предполагается, что задан текст в виде массива T[1..n] длины n и образец – в виде массива P[l..m] длины m ≤ n. Далее, предполагается, что элементы массивов P и T – символы из конечного алфавита Σ. Например, алфавит может иметь вид Σ = {0, 1} или Σ = {a, b, ..., z}. Символы массивов P и T часто называют строками или символами.
  • 3. Говорят, что образец P встречается в тексте T со сдвигом s, если 0 ≤ s ≤ n – m и T[s + l..s + m] = P[l..m] (другими словами, 1 ≤ j ≤ m T[s + j] = P[j]). Можно также сказать, что образец P встречается в тексте T, начиная с позиции s + 1. Если образец P встречается в тексте T со сдвигом s, то величину s называют допустимым сдвигом, в противном случае ее называют недопустимым сдвигом. Задача поиска подстрок – это задача поиска всех допустимых сдвигов, с которыми заданный образец P встречается в тексте T. Пример: пусть имеются образец P = abaa и текст T = abcabaabcabac. Тогда образец встречается в тексте только один раз, со сдвигом s = 3. Говорят, что сдвиг s = 3 является допустимым.
  • 4. Эффективные алгоритмы поиска подстрок производят определенную предварительную обработку представленного образца, а затем находят все допустимые сдвиги. Последняя фаза при этом будет называться поиском. В табл. 1 приведены времена предварительной обработки и сравнения для некоторых алгоритмов. Полное время работы каждого алгоритма равно сумме времени предварительной обработки и времени сравнения.
  • 5. Таблица 1 Время работы алгоритмов поиска подстрок Алгоритм Время предварительной обработки Время сравнения Простейший 0 O((n – m + 1)m) Рабина-Карпа Θ(m) O((n – m + 1)m) Конечный автомат Θ(m|Σ|) Θ(n) Кнута-Морриса-Пратта Θ(m) Θ(n)
  • 6. Обозначения и термины.  Σ* множество всех строк конечной длины, образованных с помощью символов алфавита Σ;  пустая строка конечной длины, которая обозначается ε, также принадлежит множеству Σ*;  длина строки x обозначается |x|;  конкатенация двух строк x и y, которая обозначается xy, имеет длину |x| + |y| и состоит из символов строки x, после которых следуют символы строки y.
  • 7. Говорят, что строка w – префикс строки x (обозначается w ⊂ x), если существует такая строка y ∈ Σ*, что x = wy. Заметим, что если w ⊂ x, то |w| ≤ |x|. Аналогично, строку w называют суффиксом строки x (обозначается как w ⊃ x), если существует такая строка y ∈ Σ*, что x = yw. Из соотношения w ⊃ x также следует неравенство |w| ≤ |x|. Пустая строка ε является одновременно и суффиксом, и префиксом любой строки. Примеры префикса и суффикса: ab ⊂ abcca и сса ⊃ abcca. Следует иметь в виду, что для произвольных строк x и y и для любого символа a соотношение x ⊃ y выполняется тогда и только тогда, когда xa ⊃ ya. Отношения ⊂ и ⊃ являются транзитивными.
  • 8. Лемма 1 (Лемма о перекрывающихся суффиксах). Предположим, что x, y и z – строки, для которых выполняются соотношения x ⊃ z и y ⊃ z. Если |x| ≤ |y|, то x ⊃ y. Если |x| ≥ |y|, то y ⊃ x. Если |x| = |y|, то x = y. Обозначим для краткости k-символьный префикс P[l..k] образца Р[l..m] через Pk. Таким образом, P0 = ε и Рm = Р = Р[l..m]. Аналогично, k-символьный префикс текста T обозначим Tk. С помощью этих обозначений задачу поиска подстрок можно сформулировать как задачу о выявлении всех сдвигов s в интервале 0 ≤ s ≤ n – m, таких что Р ⊃ Ts+m.
  • 9. Будем предполагать, что в псевдокоде сравнение двух строк одинаковой длины является примитивной операцией. Если строки сравниваются слева направо, и процесс сравнения прерывается, когда обнаружено несовпадение, то считается, что время, которое требуется для подобного теста, выражается линейной функцией от количества совпавших символов. Точнее говоря, считается, что тест "х = у" выполняется за время Θ(t + 1), где t – длина самой длинной строки z, такой, что z ⊂ x и z ⊂ у. Θ(t + 1) вместо Θ(t) мы пишем, чтобы учесть случай, когда t = 0 (в этой ситуации не совпадает первый же сравниваемый символ, но для проверки этого требуется некоторое положительное время). В простейшем алгоритме поиск всех допустимых сдвигов производится с помощью цикла, в котором проверяется условие Р[1..m] = T[s + 1..s + m] для каждого из n – m + 1 возможных значений s.
  • 10. Naive_String_Matcher(T, Р) 1. n ← length[T] 2. m ← length[P] 3. for s ← 0 to n – m 4. do if P[l.. m] = T[з + 1.. s + m] 5. then print "Образец обнаружен при сдвиге" s Время работы процедуры Naive_String_Matcher равно Θ((n – m +1)m) и эта оценка достаточно точна в наихудшем случае.
  • 11. Рабин и Карп предложили алгоритм поиска подстрок, показывающий на практике хорошую производительность, а также допускающий обобщения на другие родственные задачи, такие как задача о сопоставлении двумерного образца. В алгоритме Рабина-Карпа время Θ(m) затрачивается на предварительную обработку, а время его работы в наихудшем случае равно Θ((n – m +1)m). Однако с учетом определенных предположений удается показать, что среднее время работы этого алгоритма оказывается существенно лучше.
  • 12. В этом алгоритме используются обозначения из элементарной теории чисел, такие как эквивалентность двух чисел по модулю третьего числа. Для простоты предположим, что Σ = {0, 1, ..., 9}, т.е. каждый символ – это десятичная цифра (в общем случае можно предположить, что каждый символ – это цифра в системе счисления с основанием d, где d = |Σ|). После этого строку из k последовательных символов можно рассматривать как число длиной k. Таким образом, символьная строка 31415 соответствует числу 31415. Для заданного образца Р[l..m] обозначим через p соответствующее ему десятичное значение. Аналогично, для заданного текста T[1..n] обозначим через ts десятичное значение подстроки Т[s + l..s + m] длиной m при s = 0,1,..., n – m. Очевидно, что ts = р тогда и только тогда, когда Т[s + l..s + m] = Р[1..m]. Таким образом, s – допустимый сдвиг тогда и только тогда, когда ts = р. Если бы значение р можно было вычислить за время Θ(m), а все значения ts – за суммарное время Θ(n – m +1), то значения всех допустимых сдвигов можно было бы определить за время Θ(m) + Θ(n – m +1) = Θ(n) путем сравнения значения p с каждым из значений ts.
  • 13. С помощью правила Горнера величину p можно вычислить за время Θ(m): p = P[m] + 10 (P[m – 1] + 10 P[m – 2] + … + 10 (P[2] + 10P[1]) … )). Значение t0 можно вычислить из массива T[1..m] за время Θ(m) аналогичным способом. Чтобы вычислить остальные значения t1, t2, …, tn – m за время Θ(n – m), достаточно заметить, что величину ts+1 можно вычислить из величины ts за фиксированное время, так как ts + 1 = 10(ts – l0m – l T[s + 1]) + T[s + m + 1]). (1) Например, если m = 5 и ts = 31415, то нужно удалить цифру в старшем разряде T[s + 1] = 3 и добавить новую цифру в младший разряд (предположим, это цифра T[s + 5 + 1] = 2). В результате мы получаем t = 10(31415 – 10000 • 3) + 2 = 14152.
  • 14. Чтобы удалить из числа ts цифру старшего разряда, из него вычитается значение 10m–1T[s + +1]; путем умножения на 10 число сдвигается на одну позицию влево, а в результате добавления элемента T[s + m + 1] в его младшем разряде появляется нужная цифра. Это можно сделать за время Θ(m). Таким образом, число p можно вычислить за время Θ(m), величины t0, t1, t2, …, tn – m за время Θ(n – m + 1), а все вхождения образца Р[1..m] в текст T[1..n] можно найти, затратив на фазу предварительной обработки время в Θ(m), а на фазу сравнения – время Θ(n – m +1). Сложность, возникающая в этой процедуре, может быть связана с тем, что значения p и ts могут оказаться слишком большими и с ними будет неудобно работать.
  • 15. Однако эта проблема имеет простое решение: вычислять значения p и ts по модулю некоторого числа q. В этом случае величина p по модулю q вычисляется за время Θ(m), а вычисление всех величин ts по модулю q – за время Θ(n – m + 1). В качестве модуля q обычно выбирается такое простое число, для которого длина величины 10q не превышает длины машинного слова. В общем случае, если имеется d-символьный алфавит {0,1,..., d – 1}, значение q выбирается таким образом, чтобы длина величины dq не превышала длины машинного слова, и чтобы с рекуррентным соотношением (1) было удобно работать по модулю q. После этого рассматриваемое соотношение принимает вид ts + 1 = (d(ts – T[s + 1]h) + T[s + m + 1])) mod q, (2) где h ≡ dm – 1 (mod q) – значение, которое приобретает цифра «1», помещенная в старший разряд m – текстового окна.
  • 16. Недостатки: из ts = р(mod q) не следует, что ts = р. С другой стороны, если ts ≠ р(mod q), то обязательно выполняется соотношение ts ≠ р и можно сделать вывод, что сдвиг s недопустимый. Таким образом, соотношение ts = р(mod q) можно использовать в качестве быстрого эвристического теста, позволяющего исключить недопустимые сдвиги s. Все сдвиги, для которых оно справедливо необходимо подвергнуть дополнительному тестированию, чтобы проверить, что действительно ли сдвиг s является допустимым, или это просто ложное совпадение. Такое тестирование можно осуществить путем явной проверки условия Р[l..m] = T[s + l…s + m]. Если значение q достаточно большое, то можно надеяться, что ложные совпадения встречаются довольно редко и стоимость дополнительной проверки окажется низкой.
  • 17. Сформулированная ниже процедура поясняет описанные выше идеи. В роли входных значений для нее выступает текст Т, образец Р, разряд d (в качестве значения которого обычно выбирается |Σ|) и простое число q. RabinJKarp_Matcher(T, Р, d, q) 1. n ← length[T] 2. m ← length[P] 3. h ← dm – 1 mod q 4. p ← 0 5. t0 ← 0 6. for i ← 1 to m //Предварительная обработка 7. do p ← (dp + P[i]) mod q 8. t0 ← (d t0 + T[i]) mod q 9. for s ← 0 to n – m //Проверка 10. do if p = ts 11. then if P[l.. m] = T[s + 1.. s + m] 12. then print "Образец обнаружен при сдвиге" s 13. if s < n – m 14. then ts + 1← (d(ts – T[s + 1]h) + T[s + m + 1])) mod q
  • 18. Во многих приложениях ожидается небольшое количество допустимых сдвигов (возможно, выражающееся некоторой константой c). В таких приложениях математическое ожидание времени работы алгоритма равно сумме величины O((n – m +1) + cm) = O(n + m) и времени, необходимого для обработки ложных совпадений. В основу эвристического анализа можно положить предположение, что приведение значений по модулю q действует как случайное отображение множества Σ* на множество Zq.
  • 19. В таком случае можно ожидать, что число ложных совпадений равно O(n/q), потому что вероятность того, что произвольное число ts будет эквивалентно р по модулю q, можно оценить как l/q. Поскольку имеется всего O(n) позиций, в которых проверка в строке 10 дает отрицательный результат, а на обработку каждого совпадения затрачивается время O(m), математическое ожидание времени сравнения в алгоритме РабинаКарпа равно O(n) + O(m(v + n/q)), где v – количество допустимых сдвигов. Если v = O(1), a q выбрано так, что q ≥ m, то приведенное выше время выполнения равно O(n). Другими словами, если математическое ожидание количества допустимых сдвигов мало (O(1)), а выбранное простое число q превышает длину образца, то можно ожидать, что для выполнения фазы сравнения процедуре Рабина-Карпа потребуется время O(n + m). Поскольку m ≤ n, то математическое ожидание времени сравнения равно O(n).
  • 20. Представим алгоритм сравнения строк, который был предложен Кнутом, Моррисом и Праттом, и время работы которого линейно зависит от объема входных данных. В этом алгоритме используется вспомогательная функции π[1..m], которая вычисляется по заданному образцу за время Θ(m). Благодаря этому время сравнения в этом алгоритме равно Θ(n). Префиксная функция π, предназначенная для какого-нибудь образца, инкапсулирует сведения о том, в какой мере образец совпадает сам с собой после сдвигов. Эта информация позволяет избежать ненужных проверок. Поэтому в общем случае полезно знать ответ на следующий вопрос.
  • 21. Пусть символы Р[1..q] образца совпадают с символами текста T[s + l..s + q]. Чему равен наименьший сдвиг s’ > s, такой что P[l..k] = T[s' + l..s' + k], (3) где s' + к = s + q? Такой сдвиг s' – первый после сдвига s, недопустимость которого не обязательно следует из наших знаний о подстроке T[s + l..s + q]. B лучшем случае s' = s + q, и все сдвиги s + 1, s + 2, ..., s + q – 1 немедленно отбрасываются. В любом случае для нового сдвига s’ нет необходимости сравнивать первые k символов образца P и соответствующие символы текста T, поскольку их совпадение гарантированно благодаря уравнению (3).
  • 22. Необходимую информацию об образце можно получить, сдвигая его вдоль самого себя. Поскольку T[s' + l..s' + k] – известная часть текста, она является суффиксом строки Pq. Поэтому уравнение (3) можно рассматривать как запрос о максимальном значении k < q, таком, что Pk ⊃ Pq. Тогда следующий потенциально допустимый сдвиг равен s' = s + (q – к). Оказывается, что удобнее хранить количество k совпадающих при новом сдвиге s' символов, чем, например, величину s' – s. Теперь дадим формальное определение. Префиксной функцией заданного образца P[1..m] называется функция π: {1, 2, ..., m} → {0,1,..., m – 1}, такая что π[q] = max{k : k < q и Pk ⊃ Pq} .
  • 23. Другими словами, π[q] равно длине наибольшего префикса образца P, который является истинным суффиксом строки Pq. В качестве примера приведем полную префиксная функция π для образца ababababca (рис. 1). i 1 2 3 4 5 6 7 8 9 10 P[i] a b a b a b a b c a π[i] 0 0 1 2 3 4 5 6 0 1 Рис. 1. Полная префиксная функция для образца ababababca
  • 24. Алгоритм поиска подстрок Кнута-Морриса-Пратта приведен ниже в виде псевдокода процедуры KMP_Matcher. В процедуре KMP_Matcher вызывается вспомогательная процедура Compute_Prefix_Function, в которой вычисляется функция π. KMP_Matcher(T, P) 1. n ← length[T] 2. m ← length[P] 3. π ← Compute_Prefix_Function(P) 4. q ← 0 //Число совпавших символов 5. for i ← 1 to n //Сканирование текста слева направо 6. do while q > 0 и P[q + 1] ≠ T[i] 7. do q ← π[q] //Следующий символ не совпадает 8. if P[q + 1] = T[i] 9. then q ← q + 1 //Следующий символ совпадает 10. if q = m //Совпали ли все символы образца P? 11. then print "Образец обнаружен при сдвиге" i–m 12. q ← π[q] //Поиск следующего совпадения
  • 25. Compute_Prefix_Function(P) 1. m ← length[P] 2. π[1] ← 0 3. k ← 0 4. for q ← 2 to m 5. do while k > 0 и P[k + 1] ≠ P[q] 6. do k ← π[k] 7. if P[k + 1] = P[q] 8. then k ← k + 1 9. π[q] ← k 10. return π Можно формально показать, что процедура Compute_Prefix_Function выполняется за время Θ(m), а время выполнения сравнений в процедуре KMP_Matcher равно Θ(n). Также можно формально доказать корректность приведенных процедур, с помощью которых реализуется алгоритм Кнута-Морриса-Пратта.