Sei sulla pagina 1di 25

STRING MATCHING

Aditya Pratap Singh


215/CO/15
Netaji Subhas Institute Of Technology
CONTENTS

● Introduction
● String Matching
● Basic Classification
● Naive Algorithm
● Rabin-Karp Algorithm
○ String Hashing
○ Hash value for substrings
● Knuth-Morris-Pratt Algorithm
○ Prefix Function
○ KMP Matcher
● Summary
INTRODUCTION

● String matching algorithms are an important class of string


algorithms that tries to find one or many indices where one
or several strings(or patterns) are found in the larger string(or
text)

● Why do we need string matching?


String matching is used in various applications like spell
checkers, spam filters, search engines, plagiarism detectors,
bioinformatics and DNA sequencing etc.
STRING MATCHING

● To find all occurrences of a pattern in a given text


● Formally, given a pattern P[1..m] and a text T[1..n], find all
occurrences of P in T. Both P and T belongs to Σ*
● P occurs with shift s(beginning at s+1): P[1] = T[s+1], P[2] =
T[s+2],…, P[m] = T[s+m]
● If so, s is called a valid shift, otherwise an invalid shift
● Note: one occurrence can start within another one ie.
overlapping is allowed. eg P=abab T=abcabababbc, P occurs
at s=3 and s=5.

*text is the string that we are searching


*pattern is the string that we are searching for
*Shift is an offset into a string
BASIC CLASSIFICATION

1. Naive Algorithm - The naive approach is accomplished by


performing a brute-force comparison of each character in the
pattern at each possible placement of the pattern in the
string. It is O(mn) in the worst case scenario

2. Rabin-Karp Algorithm - It compares the string’s hash values,


rather than string themselves. Performs well in practice and
generalized to other algorithm for related problems such as
2D-string matching

3. Knuth-Morris-Pratt Algorithm - It is improved on brute-force


algorithm and is capable of running O(m+n) in the worst
case. It improves the running time by taking advantage of
prefix function
NAIVE ALGORITHM

One of the most obvious approach towards the string matching


problem would be to compare the first element of the pattern to
be searched ‘p’, with the first element of the string ‘s’ in which to
locate ‘p’.

If the first element of ‘p’ matches the first element of ‘s’ ,


compare the second element and so on. If match found proceed
likewise until entire ‘p’ is found. If a mismatch is found at any
position , shift index to one position to the right and continue
comparison

This approach is easy to understand and implement but it can be


too slow in some cases.
In worst case it may take (m*n) iterations to complete the task.
PSEUDOCODE

function naive(text[], pattern[]){


for(i = 0; i < n; i++) {
for(j = 0; j < m && i + j < n; j++) {
if(text[i + j] != pattern[j]) break; // mismatch found
if(j == m) // match found
}
}
}
ILLUSTRATION

String S = a b c a b a a b c a b a c
Pattern P = a b a a

Step 1: Compare P[1] with S[1]


abcabaabcabac

abaa

Step 2: Compare P[2] with S[2]


abcabaabcabac

abaa
ILLUSTRATION

Step 3: Compare P[3] with S[3]


abcabaabcabac

abaa

Since mismatch is detected, shift ‘p’ one position to the left and
perform steps analogous to those from step 1 to step 3. At
position where mismatch is detected, shift ‘p’ one position to
right and repeat matching procedure.
ILLUSTRATION

Finally, a match is found after shifting ‘p’ three times to the right
side.

abcabaabcabac

abaa

Drawbacks : If ‘m’ is the length of pattern P and ‘n’ is the length


of text T, then the matching time is O(n*m), which is certainly a
very slow running time
RABIN-KARP ALGORITHM

This is actually the naive approach augmented with a powerful


programming technique - hash function

Algorithm :
1. Calculate the hash for the pattern P
2. Calculate the hash values for all the prefixes of the text T.
3. Now, we can compare a substring of length |s| in constant
time using the calculated hashes.

This algorithm was authored by Michael Rabin and Richard Karp


in 1987.
STRING HASHING

Problem - Given a string S of length n = |S| . Calculate the hash


value of S

Solution -

where p and m are suitably chosen prime numbers.


CHOICE OF PARAMETERS

‘p’ should be taken roughly equal to the number of characters in


the input alphabet. If input is composed of only lowercase
characters of English alphabet, p=31 is a good choice. If the
input may contain both uppercase and lowercase letters, then
p=53 is a good choice.

‘m’ should be a large prime. A popular choice is m = 10^9+7


This is a large number but still small enough so that we can
perform multiplication of two values using 64 bit integers.
HASH CALCULATION OF SUBSTRINGS OF GIVEN STRING

Problem : Given string S and indices i and j . Find the hash value
of S[i..j]

Solution :
By definition we have,

Multiplying by pi gives,

So by knowing the hash value of each prefix of string S, we can


compute the hash of any substring in constant O(1) time.
PSEUDOCODE
vector<int> rabin_karp(string const& pat, string const& text) {
const int p = 31, m = 1e9 + 9;
int S = pat.size(), T = text.size();

vector<long long> p_pow(max(S, T));


p_pow[0] = 1;
for (int i = 1; i < (int)p_pow.size(); i++)
p_pow[i] = (p_pow[i-1] * p) % m;

vector<long long> h(T + 1, 0);


for (int i = 0; i < T; i++)
h[i+1] = (h[i] + (text[i] - 'a' + 1) * p_pow[i]) % m;
long long h_s = 0;
for (int i = 0; i < S; i++)
h_s = (h_s + (pat[i] - 'a' + 1) * p_pow[i]) % m;

vector<int> occurrences;
for (int i = 0; i + S - 1 < T; i++) {
long long cur_h = (h[i+S] + m - h[i]) % m;
if (cur_h == h_s * p_pow[i] % m)
occurrences.push_back(i);
}
return occurrences;
}
KNUTH-MORRIS-PRATT ALGORITHM

Knuth, Morris and Pratt proposed a linear time algorithm for the
string matching problem.

A matching time of O(n) is achieved by avoiding comparisons with


elements of ‘S’ that have previously been involved in comparison
with some element of the pattern ‘p’ to be matched ie.
backtracking on the string ‘S’ never occurs.

KMP makes use of ‘prefix function’


PREFIX FUNCTION

The prefix function of a string is defined as an array Ⲡ of length n,


where Ⲡ[i] is the length of the longest proper prefix of the
substring s[0..i] which is also a suffix of this substring.
A proper prefix of a string is a prefix that is not equal to the string
itself. So by definition Ⲡ[0] = 0

Mathematically,
EXAMPLE
S = “aabaaab”
PREFIX Ⲡ[i]

a a 0

aa aa 1

aab aab 0

aaba aaba 1

aabaa aabaa 2

aabaaa aabaaa 2

aabaaab aabaaab 3
ALGORITHM TO COMPUTE PREFIX FUNCTION

● We compute the prefix values Ⲡ[i] in a loop iterating from i=1


to i=n-1 (Ⲡ[0] just gets assigned with 0)

● To calculate the current value Ⲡ[i] we set the variable j


denoting the length of the best suffix for ‘i-1’ . Initially j = Ⲡ[i-1]

● Test if the suffix of length ‘j+1’ is also a prefix by comparing s[j]


and s[i]. If they are equal then we assign Ⲡ[i] = j+1 . Otherwise,
we reduce j to Ⲡ[j-1] and repeat this step.

● If we have reached the length j=0 and still don’t have the
match, then we assign Ⲡ[i] = 0 and go to the next index ‘i+1’
PSEUDOCODE

vector<int> prefix_function(string s){


int n = (int)s.length();
vector<int> pi(n);
for(int i=1;i<n;i++){
int j = pi[i-1];
while(j>0 and s[i]!=s[j]) j = pi[j-1];
if(s[i] == s[j]) ++j;
pi[i] = j;
}
return pi;
}

Runtime - O(n)
KMP MATCHER

● This is a classical application of prefix function, which we just


learned
● Given text T and string S, we need to find all occurrences of S
in T
● Denote with n the length of the string S and with m the length
of the string T ie. n = |S| and m = |T|
● Generate a string S + # + T , where # is a separator that neither
appears in S nor T . Now calculate the prefix function of this
string
● By definition, Ⲡ[i] in this string corresponds to the largest block
that coincides with S and ends at position ‘i’ .
● Note: Ⲡ[i] can not be larger than ‘n’ because of the separator #
that we used
● If Ⲡ[i] == n, then we can say that string S appears completely at
this position.
EXAMPLE

S = “aba”
T = “aababac”
Generated string(G) = “aba#aababac”
Index (i) PREFIX Ⲡ[i]

4 a 1

5 aa 1

6 aab 2

7 aaba 3

8 aabab 2

9 aababa 3

10 aababac 0

Ⲡ[i] = n(=3) at positions i = 7 and 9 of G , which means at indices


i = 1 and i=3 in the Text , there is occurrence of the pattern(S)
PSEUDOCODE

vector<int> kmp(string pattern,string text){


string str = pattern + "#" + text;
int n = pattern.length(), m = str.length();
vector<int> pi = prefix_function(str);
vector<int> ret;
for(int i=n+1;i<m;i++) {
if(pi[i] == n) ret.pb(i-2*n);
}
return ret;
}

Runtime: O(n+m)
SUMMARY

Algorithm Time Complexity Key Ideas Approach

Brute Force (Naive) O(m*n) Searching with all Linear Searching


alphabets

Rabin-Karp Θ(m+n) Compare the text Hashing Based


and patterns using
their hash functions

Knuth-Morris-Pratt O(m+n) Constructs an Heuristic Based


automaton from the
pattern

n = |pattern| , length of pattern


m = |text| , length of text
THANK YOU

Potrebbero piacerti anche