Python/파이썬 자료구조 알고리듬

35. 동적 계획법 - 가장 긴 공통 문자열 찾기 - 파이썬

컴닥 2019. 6. 29. 07:58
반응형

동적 프로그래밍((dynamic programming)을 적용하기 쉬운(?) 예제로 가장 긴 공통 문자열 찾기(LCS, Longest Common Substring)가 있습니다. 

 

두 개의 문자열 'abcde'와 'cdefg'에서 가장 긴 공통 문자열은 'cde'입니다. 

 

먼저 전수조사(brute force)로 답을 찾아보고 문제점을 알아봅니다. 

def make_set(s):
    result = set()
    for i in range(len(s)):
        for j in range(i + 1, len(s) + 1):
            result.add(s[i:j])
    return result


def lcs(s1, s2):
    max_len = 0
    max_str = ''
    for each in make_set(s1) & make_set(s2):
        if len(each) > max_len:
            max_len = len(each)
            max_str = each
    print(max_len, max_str)
    # return max_len, max_str


lcs('abcde', 'cdeab')

파이썬의 집합 자료형을 이용하는 방법입니다.
집합 자료형은 원소의 중복을 막아줍니다. 
그리고 교집합을 이용하면 이 문제를 간단히 풀 수 있습니다. 

 

흔히 말하는 노가다 전수조사형 풀이법입니다.
가능한 문자열을 모두 뽑아서 비교합니다.

 

하지만 조금만 단어가 길어져도
시간이 너무 오래 걸립니다. 

 

(최대 공통 문자열의 길이만) 재귀적으로 풀어봅니다. 

# Longest Common Substring / recursive


def lcs(i, j):
    if i == word1_length or j == word2_length:
        return 0
    if word1[i] == word2[j]:
        return 1 + lcs(i + 1, j + 1)
    else:
        return max(lcs(i + 1, j), lcs(i, j + 1))


word1 = 'abcde'
word2 = 'cdefab'
word1_length = len(word1)
word2_length = len(word2)
print(lcs(0, 0))  # 3

 

(최대 공통 문자열의 길이만) 리스트(배열)를 이용해 풀어봅니다. 

공통 문자열의 길이를 저장할 2차원 리스트가 핵심입니다. 
공통 문자열이 길어질수록 저장되는 숫자도 1씩 커집니다. 

이 2가지만 이해하면 코딩은 어렵지 않습니다. 

def show(array):
    for i in array:
        print(i)


def lcs_dp():
    array = [[0 for _ in range(len(word2)+1)] for _ in range(len(word1)+1)]
    for i in range(len(word1)):
        for j in range(len(word2)):
            if word1[i] == word2[j]:
                array[i+1][j+1] = array[i][j] + 1
    show(array)


word1 = 'abcde'
word2 = 'cdefab'

lcs_dp()
[0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 1, 0]
[0, 0, 0, 0, 0, 0, 2]
[0, 1, 0, 0, 0, 0, 0]
[0, 0, 2, 0, 0, 0, 0]
[0, 0, 0, 3, 0, 0, 0]

위 배열을 보면 공통 문자열이 길어질수록 사선 방향(↘)으로 숫자가 커집니다.

3이 가장 큰 숫자군요. 이 숫자가 가장 긴 문자열의 길이가 됩니다. 

그리고 3의 배열 내에서의 위치(정확하게 말하면 1을 빼야 함)가 문자열의 마지막 글자의 위치가 됩니다. 

 

문자열까지 찾을 수 있도록 코드를 추가합니다. 

# Longest Common Substring / dynamic programming


def lcs(word1, word2):
    max = 0
    index = 0
    letters = [[0 for _ in range(len(word2) + 1)] for _ in range(len(word1) + 1)]
    for i in range(len(word1)):
        for j in range(len(word2)):
            if word1[i] == word2[j]:
                letters[i+1][j+1] = letters[i][j] + 1
            if max < letters[i+1][j+1]:
                max = letters[i+1][j+1]
                index = i + 1
    return word1[index-max:index]


print(lcs('aabcc', 'babcd'))
abc

아까 말씀드렸던 가장 큰 숫자 3을 별도의 반복 없이 기존 for문 내에서 찾을 수 있도록 코딩합니다. 

가장 큰 숫자를 찾으면 그때의 인덱스도 같이 저장합니다. 

반응형