Лекция 15 Поиск подстрок
-
Upload
simplepeople -
Category
Data & Analytics
-
view
117 -
download
1
Transcript of Лекция 15 Поиск подстрок
Анализ комбинаторных алгоритмов
Лекция № 15Поиск подстрок.
Задача поиска подстрок
Строками (или словами) называют массивы состоящие из символов некоторого конечного алфавита Σ.
Пусть даны две строки: текст T[1..n] длины n и образец P[1..m] длины m<n.
Говорят, что образец P входит со сдвигом s (или входит с позиции s+1) в текст T, если 0 ≤ s ≤ n-m и верно T[s+1..s+m] = P[1..m].
Задача поиска подстрок
Если P входит в T со сдвигом s,то говорят, что s – допустимый сдвиг.
В противном случае s называют недопустимым сдвигом.
Задача поиска подстрок состоит в нахождении всех допустимых сдвигов для заданных текста T и образца P.
Обозначения и терминология
Через Σ* обозначается множество всех конечных строк в алфавите Σ, включая пустую строку.
Соединение (конкатенация) строк x и у обознается xy.
Говорят, что w префикс x (w x), если x=wy, для некоторого y из Σ*.
Говорят, что w суффикс x (w x), если x=yw, для некоторого y из Σ*.
Обозначения и терминология
ТеоремаПусть x, y, z – строки, для которых верно x z, y z. Тогда x y, если |x|<|y|, y x, если |y|<|x| и y=x, если |y|=|x|
Простейший алгоритм
void StringMatcher(T,P,n,m){ for(s=0; s<=n-m; s++){ b = true; for(i=1; i<=m; i++) b = b & (T[s+i]==P[i]); if (b) print(“подстрока входит со сдвигом”, s); }}
a c a a b c
a a b
a c a a b c
a a b
a c a a b c
a a b
Алгоритм Рабина-Карпа
Алгоритм Рабина-Крапа предполагает, что Σ={0,1,2…d-1}
Строкам T, P и всем подстрокам T длины s можно сопоставить числа t, p, ts.
Эти числа можно найти за время O(m), где m – длина строки, используя схему Горнера:p = P[m]+d(P[m-1]+d(P[m-2]+…+d P[1])…)
Алгоритм Рабина-Карпа
Числа t1,t2,…tn-m можно вычислить за время O(n-m), поскольку число ts+1 можно вычислить за O(1) считая известным ts
ts+1 = d(ts – dm-1 T[s+1] ) + T[s+m+1]
Для определения вхождения достаточно сравнить числа p и ts.
Для того чтобы уйти от операций с большими числами используют арифметику по модулю q (простое число)
Алгоритм Рабина-Карпа
void RabinKarpMatcher(T,P,n,m,d,q){ h = ModExp(d,m-1,q); p = 0; t[0]=0; for(i=1; i<=n; i++){ p = (d*p+P[i]) % q; t[0]= (d*t[0]+T[i]) % q; } for(s=0; s<=n-m; s++){ if(p==t[s]){ b = true; for(i=1; i<=m; i++) b = b & (T[s+i]==P[i]); if(b){print(“подстрока входит со сдвигом”, s);} } if (s<n-m) t[s+1] = (d*(t[s]-T[s+1]*h)+T[s+m-1])%q; }}
Алгоритм Рабина-Карпа
2 3 5 9 0 2 3 1 4 1 5 2 6 7 3 9 9 2 1
8 9 3 11 0 1 7 8 4 5 10 11 7 9 11
Mod 13
Конечные автоматы
Конечный детерминированный автомат (F-схема) – определяется пятеркой чисел M=(Q,q0,A,Σ,δ), где:Q – конечное множество состоянийq0 – начальное состояниеA – множество допускающих
состоянийΣ – конечный входной алфавитδ – функция переходов Q x Σ.
Конечные автоматы
Конечный автомат распознающий образец P в тексте строится следующим образом: Определяется суффикс-функция σ, ставящая в
соответствие строке x максимальную длину суффикса x являющегося префиксом P.
Множество состояний Q = {0,1,…,m}. Начальное состояние q0 = 0, единственное разрешающее состояние m
Функция переходов определена как δ(q,a) = σ(Pq,a)
Конечные автоматы
P = «ababaca»
0 1 2 3 4 5 6 7a
a
b aa
b aa a
bb
c a
0 1 2 3 4 5 6 7a 1 1 3 1 5 1 7 1b 0 2 0 4 0 4 0 2c 0 0 0 0 0 6 0 0
Конечные автоматы
void FiniteAutomatonMatcher(T,n,m){ q = 0; ComputeTF(P,m,sigma,l); for(i=0; i<=n; i++){ q = TF[q, T[i]]; if (q == m) print(“подстрока входит со сдвигом”, i-m); }}void ComputeTF(P,m,sigma,l){ for(q=0; q<=m; q++){ for(i=1; i<=l; i++){ k = min(m+1,q+2); do{k--;}while(SuffixFunc(P,k,P,q,a)); // пока Pk – суффикс Pq+a TF[q,a]=k; } }}
Алгоритм Кнута-Морриса-Пратта
b a c c a b a b a b a c a b a
a b a b a c a
b a c c a b a b a b a c a b a
a b a b a c a
q
k
s
s`
s` = s + (q-k)
Алгоритм Кнута-Морриса-Пратта
Алгоритм Кнута-Морриса-Пратта работает за линейное время O(n+m)
Алгоритм использует понятие префикс-функции.
Префикс-функция π(q) – длина наиболь-шего префикса P, являющегося собст-венным суффиксом Pq: π(q) = max{k: k<q и Pk Pq}
Алгоритм Кнута-Морриса-Пратта
void KMPMatcher(T,P,n,m){ PF = ComputePrefixFunc(P,m); q=0; for(i=0; i<=n; i++){ while((q>0)&&(P[q+1]!=T[i])) q = PF[q]; if(P[q+1]==T[i]) q++; if(q==m) {print(“подстрока входит со сдвигом”, i-m); q = PF[q]; } }}void ComputePrefixFunc(P,m){ PF[1]=0; k=0; for(q=2;q<=m;q++){ while((k>0)&&(P[k+1]!=P[q])) k = PF[k]; if(P[k+1]=P[q]) k++; PF[q] = k; }}
Алгоритм Бойера-Мура
w r i t t e n _ n o t i c e _ t h a t
r e m i n i s c e n c e
Стоп символ Безопасный суффикс
s
w r i t t e n _ n o t i c e _ t h a t
r e m i n i s c e n c es+4
w r i t t e n _ n o t i c e _ t h a t
r e m i n i s c e n c es+3
Алгоритм Бойера-Мура
void ComputeLastOccFunc(P,m,Sigma,lambda){ for(i=0; i<Sigma->size; i++) Lambda[i]=0; for(j=1; j<=m; m++){ Lambda[P[j]]=j; }}
Эвристика стоп-символа предлагает сдвинуть образец на такое расстояние, чтобы стоп-символ в тексте оказался на против крайнего правого вхождения этого символа в образец.
Алгоритм Бойера-Мура
void ComputeGoodSuffixFunc(P,m,gamma){ pi = ComputePrefixFunc(P,m) P1 = ReverseString(P); pi1= ComputePrefixFunc(P1,m); for(j=0; j<=m; j++) gamma[j]= m-pi[m]; for(i=1; i<=m; i++){ j = m – pi1[i]; if(gamma[j]>i-pi1[i]) gamma[j]=i-pi1[i]; }}
Эвристика безопасного суффикса предлагает сдвинуть образец вправо так, чтобы ближайшее (справа налево) вхождение безопасного суффикса в образец оказалось напротив суффикса в тексте.
Алгоритм Бойера-Мура
void BoyerMooreMatcher(T,P,Sigma,n,m){ ComputeLastOccFunc(P,m,Sigma,lambda); ComputeGoodSuffixFunc(P,m,gamma); s =0 while(s<=n-m){ j = m; while((j>0)&&(P[j]=T[s+i])) j--; if(j=0){ print(“подстрока входит со сдвигом”,s); s = s+gamma[0]; } else s = s + max(gamma[j],j-lambda[T[s+j]]); }}