ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Algorithm] 알고리즘 - 문자열 매칭(Pattern Matching)
    Study/Algorithm 2020. 8. 27. 19:00

    문자열 매칭 또는 패턴 매칭은 컴퓨터 과학에서 중요한 문제이다. 노트패드나 워드파일 또는 웹 브라우저 데이터베이스에서 문자열을 검색할 때 패턴 매칭 알고리즘을 사용하여 검색 결과를 표시한다.

     

    문자열 매칭 방법의 종류는 아래와 같다.

     

    1. Naive Matching (원시적인 매칭) - $O(mn)$

    2. Automata Algorithm (오토마타를 이용한 매칭) - $\Theta(n + |\sum| m)$

    3. Rabin-Karp Algorithm (라빈-카프 알고리즘) - $\Theta(n)$

    4. Knuth-Morris-Pratt(KMP) Algorithm (KMP알고리즘) - $\Theta(n)$

    5. Boyer-Moore Algorithm (보이어-무어 알고리즘) - $\Theta(n)$ Worst Case: $\Theta(nm)$

     

    하나씩 알아보자.

     

    Naive Matching (원시적인 매칭)

    txt[0...n-1] text와 pat[0...m-1] pattern이 주어질 때 txt를 하나씩 접근하여 pat와 일치하는지 확인하는 것이다. 

    https://www.geeksforgeeks.org/naive-algorithm-for-pattern-searching/

     

    Best Case는 pat의 첫 번째 문자가 txt에 없는 경우이다. 이때 시간 복잡도는 $O(n)$이다. 

    Worst Case는 txt가 'AAAAAAAAAAAAAAAAAAAAAAA' pat이 'AAAAA'일 때 처럼 txt의 모든 문자가 pat가 같을 때 이다. 

    pat이 'AAAAB'처럼 pat의 마지막 글자가 달라도 최악의 경우이다.

     

    아래는 파이썬으로 구현한 Navie Matching 알고리즘이다. 

    # Searching algorithm 
    def search(pat, txt): 
        M = len(pat) 
        N = len(txt) 
      
        # A loop to slide pat[] one by one */ 
        for i in range(N - M + 1): 
            j = 0
              
            # For current index i, check  
            # for pattern match */ 
            while(j < M): 
                if (txt[i + j] != pat[j]): 
                    break
                j += 1
      
            if (j == M):  
                print("Pattern found at index ", i) 
      
    # Driver Code 
    if __name__ == '__main__': 
        txt = "AABAACAADAABAAABAA"
        pat = "AABA"
        search(pat, txt) 
      
    # This code is contributed  
    # by PrinciRaj1992 

     

    Finite Automata Based Algorithm (오토마타를 이용한 매칭)

    패턴을 전처리하고 Finite Automata를 나타내는 2차원 배열을 만든다. FA를 구성하는게 까다로운데 일단 FA를 만들고 나면 검색은 간단하게 된다. 검색할 때 txt의 첫 문자와 오토마타의 첫 번째 상태에서 시작한다. 모든 단계에서 텍스트의 다음  글자를 고려해서 만들어진 FA에서 다음 상태를 찾고 새로운 상태로 움직이면 된다. 

     

    FA의 State의 개수는 M(패턴의 길이) +1이다. FA를 구성할 때 고려해야 할 주요한 부분은 가능한 모든 문자에 대해 현재 state에서 다음 state를 얻는 것이다. 

     

    # Python program for Finite Automata  
    # Pattern searching Algorithm 
      
    NO_OF_CHARS = 256
      
    def getNextState(pat, M, state, x): 
        ''' 
        calculate the next state  
        '''
      
        # If the character c is same as next character  
          # in pattern, then simply increment state 
      
        if state < M and x == ord(pat[state]): 
            return state+1
      
        i=0
        # ns stores the result which is next state 
      
        # ns finally contains the longest prefix  
         # which is also suffix in "pat[0..state-1]c" 
      
         # Start from the largest possible value and  
          # stop when you find a prefix which is also suffix 
        for ns in range(state,0,-1): 
            if ord(pat[ns-1]) == x: 
                while(i<ns-1): 
                    if pat[i] != pat[state-ns+1+i]: 
                        break
                    i+=1
                if i == ns-1: 
                    return ns  
        return 0
      
    def computeTF(pat, M): 
        ''' 
        This function builds the TF table which  
        represents Finite Automata for a given pattern 
        '''
        global NO_OF_CHARS 
      
        TF = [[0 for i in range(NO_OF_CHARS)]\ 
              for _ in range(M+1)] 
      
        for state in range(M+1): 
            for x in range(NO_OF_CHARS): 
                z = getNextState(pat, M, state, x) 
                TF[state][x] = z 
      
        return TF 
      
    def search(pat, txt): 
        ''' 
        Prints all occurrences of pat in txt 
        '''
        global NO_OF_CHARS 
        M = len(pat) 
        N = len(txt) 
        TF = computeTF(pat, M)     
      
        # Process txt over FA. 
        state=0
        for i in range(N): 
            state = TF[state][ord(txt[i])] 
            if state == M: 
                print("Pattern found at index: {}".\ 
                       format(i-M+1)) 
      
    # Driver program to test above function             
    def main(): 
        txt = "AABAACAADAABAAABAA"
        pat = "AABA"
        search(pat, txt) 
      
    if __name__ == '__main__': 
        main() 
      
    # This code is contributed by Atul Kumar 

    Rabin-Karp Algorithm (라빈-카프 알고리즘)

    Naive 매칭 방법과 같이 라빈카프 알고리즘도 하나씩 문자를 이동하면서 모든 문자에 대해 패턴과 일치하는지 확인한다.

    그러나 Naive 매칭과 다른 점은 패턴의 해시값을 쓴다는 것이다. 

     

    Rabin-Karp가 제안한 해시 함수는 정수값을 계산한다. 그러나 가능한 문자의 수는 10개 보다 많고 패턴 길이는 수 있다. 심하면 컴퓨터 레지스터의 용량이 초과될 수 있거나 오버플로우가 발생할 수 있다. 이에 대한 해결책은 modulo를 사용하는 것이다.

     

    # Following program is the python implementation of 
    # Rabin Karp Algorithm given in CLRS book 
      
    # d is the number of characters in the input alphabet 
    d = 256
      
    # pat  -> pattern 
    # txt  -> text 
    # q    -> A prime number 
      
    def search(pat, txt, q): 
        M = len(pat) 
        N = len(txt) 
        i = 0
        j = 0
        p = 0    # hash value for pattern 
        t = 0    # hash value for txt 
        h = 1
      
        # The value of h would be "pow(d, M-1)%q" 
        for i in range(M-1): 
            h = (h*d)%q 
      
        # Calculate the hash value of pattern and first window 
        # of text 
        for i in range(M): 
            p = (d*p + ord(pat[i]))%q 
            t = (d*t + ord(txt[i]))%q 
      
        # Slide the pattern over text one by one 
        for i in range(N-M+1): 
            # Check the hash values of current window of text and 
            # pattern if the hash values match then only check 
            # for characters on by one 
            if p==t: 
                # Check for characters one by one 
                for j in range(M): 
                    if txt[i+j] != pat[j]: 
                        break
      
                j+=1
                # if p == t and pat[0...M-1] = txt[i, i+1, ...i+M-1] 
                if j==M: 
                    print "Pattern found at index " + str(i) 
      
            # Calculate hash value for next window of text: Remove 
            # leading digit, add trailing digit 
            if i < N-M: 
                t = (d*(t-ord(txt[i])*h) + ord(txt[i+M]))%q 
      
                # We might get negative values of t, converting it to 
                # positive 
                if t < 0: 
                    t = t+q 
      
    # Driver program to test the above function 
    txt = "GEEKS FOR GEEKS"
    pat = "GEEK"
    q = 101 # A prime number 
    search(pat,txt,q) 
      
    # This code is contributed by Bhavya Jain 

     

    Knuth-Morris-Pratt(KMP) Algorithm (KMP알고리즘)

    오토마타를 이용한 매칭과 동기가 유사하지만 준비 작업이 더 단순하다. 패턴의 각 위치에 대해 매칭에 실패했을 때 돌아갈 곳을 준비해 둔다.

    # Python program for KMP Algorithm 
    def KMPSearch(pat, txt): 
        M = len(pat) 
        N = len(txt) 
      
        # create lps[] that will hold the longest prefix suffix  
        # values for pattern 
        lps = [0]*M 
        j = 0 # index for pat[] 
      
        # Preprocess the pattern (calculate lps[] array) 
        computeLPSArray(pat, M, lps) 
      
        i = 0 # index for txt[] 
        while i < N: 
            if pat[j] == txt[i]: 
                i += 1
                j += 1
      
            if j == M: 
                print("Found pattern at index " + str(i-j)) 
                j = lps[j-1] 
      
            # mismatch after j matches 
            elif i < N and pat[j] != txt[i]: 
                # Do not match lps[0..lps[j-1]] characters, 
                # they will match anyway 
                if j != 0: 
                    j = lps[j-1] 
                else: 
                    i += 1
      
    def computeLPSArray(pat, M, lps): 
        len = 0 # length of the previous longest prefix suffix 
      
        lps[0] # lps[0] is always 0 
        i = 1
      
        # the loop calculates lps[i] for i = 1 to M-1 
        while i < M:
            if pat[i]== pat[len]: 
                len += 1
                lps[i] = len
                i += 1
            else: 
                # This is tricky. Consider the example. 
                # AAACAAAA and i = 7. The idea is similar  
                # to search step. 
                if len != 0: 
                    len = lps[len-1]
                    # Also, note that we do not increment i here 
                else: 
                    lps[i] = 0
                    i += 1
      
    txt = "ABABDABACDABABCABAB"
    pat = "ABABCABAC"
    KMPSearch(pat, txt) 
    

     

    Boyer-Moore Algorithm (보이어-무어 알고리즘)

    보이어 무어 알고리즘도 패턴을 전처리한다. 그러나 이전의 매칭 알고리즘과는 다르게 텍스트 무자를 다 보지 않아도 된다. 

     

    Bad Character Heuristic 최악의 경우 시간이 O(mn) 걸릴 수 있다. 텍스트와 패턴의 모든 문자가 동일할 때 최악의 경우가 발생한다. 예를 들어 txt [] = "AAAAAAAAAAAAAAAAA" 및 pat [] = "AAAAA"와 같은 경우이다. 

     

    참고자료:

    http://nlp.jbnu.ac.kr/AL/ch10.pdf

    https://www.geeksforgeeks.org/naive-algorithm-for-pattern-searching/

    'Study > Algorithm' 카테고리의 다른 글

    자료구조 및 알고리즘 시각화  (0) 2020.08.19
    알고리즘 소개  (0) 2020.05.06

    댓글

Designed by Tistory.