ݺߣ

ݺߣShare a Scribd company logo
Основные алгоритмические
стратегии (продолжение)

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 (20)

ЗАМЕЧАНИЕ К ПОСТРОЕНИЮ НЕ ИМЕЮЩЕГО РИСКА ПОРТФЕЛЯ
ЗАМЕЧАНИЕ К ПОСТРОЕНИЮ НЕ ИМЕЮЩЕГО РИСКА ПОРТФЕЛЯ ЗАМЕЧАНИЕ К ПОСТРОЕНИЮ НЕ ИМЕЮЩЕГО РИСКА ПОРТФЕЛЯ
ЗАМЕЧАНИЕ К ПОСТРОЕНИЮ НЕ ИМЕЮЩЕГО РИСКА ПОРТФЕЛЯ
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_lecture04
Computer Science Club
Лекция №14. Графы: кратчайшие пути и максимальные потоки. Предмет "Структуры ...
Лекция №14. Графы: кратчайшие пути и максимальные потоки. Предмет "Структуры ...Лекция №14. Графы: кратчайшие пути и максимальные потоки. Предмет "Структуры ...
Лекция №14. Графы: кратчайшие пути и максимальные потоки. Предмет "Структуры ...
Nikolay Grebenshikov
предел последовательности
предел последовательностипредел последовательности
предел последовательности
tomik1044
5
55
5
ssusera868ff
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
Computer Science Club
Proizvodnaja
ProizvodnajaProizvodnaja
Proizvodnaja
ShishkinaIrina
Лекция №2. Алгоритмические проблемы. Стандартные схемы программ. Предмет "Тео...
Лекция №2. Алгоритмические проблемы. Стандартные схемы программ. Предмет "Тео...Лекция №2. Алгоритмические проблемы. Стандартные схемы программ. Предмет "Тео...
Лекция №2. Алгоритмические проблемы. Стандартные схемы программ. Предмет "Тео...
Nikolay Grebenshikov
Линейная алгебра - II
Линейная алгебра - IIЛинейная алгебра - II
Линейная алгебра - II
DEVTYPE
Основы теории графов 11: гамильтоновы циклы
Основы теории графов 11: гамильтоновы циклыОсновы теории графов 11: гамильтоновы циклы
Основы теории графов 11: гамильтоновы циклы
Alex Dainiak
слайды к лаб1 тмм
слайды к лаб1 тммслайды к лаб1 тмм
слайды к лаб1 тмм
student_kai
ЗАМЕЧАНИЕ К ПОСТРОЕНИЮ НЕ ИМЕЮЩЕГО РИСКА ПОРТФЕЛЯ
ЗАМЕЧАНИЕ К ПОСТРОЕНИЮ НЕ ИМЕЮЩЕГО РИСКА ПОРТФЕЛЯ ЗАМЕЧАНИЕ К ПОСТРОЕНИЮ НЕ ИМЕЮЩЕГО РИСКА ПОРТФЕЛЯ
ЗАМЕЧАНИЕ К ПОСТРОЕНИЮ НЕ ИМЕЮЩЕГО РИСКА ПОРТФЕЛЯ
Ilya Gikhman
Скорость роста функций
Скорость роста функцийСкорость роста функций
Скорость роста функций
DEVTYPE
Лекция 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_lecture04
Computer 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_lecture03
Computer Science Club
Лекция №2. Алгоритмические проблемы. Стандартные схемы программ. Предмет "Тео...
Лекция №2. Алгоритмические проблемы. Стандартные схемы программ. Предмет "Тео...Лекция №2. Алгоритмические проблемы. Стандартные схемы программ. Предмет "Тео...
Лекция №2. Алгоритмические проблемы. Стандартные схемы программ. Предмет "Тео...
Nikolay Grebenshikov
Линейная алгебра - II
Линейная алгебра - IIЛинейная алгебра - II
Линейная алгебра - II
DEVTYPE
Основы теории графов 11: гамильтоновы циклы
Основы теории графов 11: гамильтоновы циклыОсновы теории графов 11: гамильтоновы циклы
Основы теории графов 11: гамильтоновы циклы
Alex Dainiak
слайды к лаб1 тмм
слайды к лаб1 тммслайды к лаб1 тмм
слайды к лаб1 тмм
student_kai

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

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

More from student_kai (20)

презентация
презентацияпрезентация
презентация
student_kai
презентации продолжение банкета
презентации продолжение банкетапрезентации продолжение банкета
презентации продолжение банкета
student_kai
основы программирования на языке C
основы программирования на языке Cосновы программирования на языке C
основы программирования на языке C
student_kai
презентация курсовой работы
презентация курсовой работыпрезентация курсовой работы
презентация курсовой работы
student_kai
презентация
презентацияпрезентация
презентация
student_kai
презентации продолжение банкета
презентации продолжение банкетапрезентации продолжение банкета
презентации продолжение банкета
student_kai
основы программирования на языке C
основы программирования на языке Cосновы программирования на языке C
основы программирования на языке C
student_kai
презентация курсовой работы
презентация курсовой работыпрезентация курсовой работы
презентация курсовой работы
student_kai

лекция 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). Также можно формально доказать корректность приведенных процедур, с помощью которых реализуется алгоритм Кнута-Морриса-Пратта.