ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Dynamic Programming(1)
    알고리즘 2020. 6. 19. 01:13

    다이나믹 프로그래밍의 가장 큰 컨셉은

    "subproblem의 결과를 저장해서 사용한다"

     

    그래서, 2가지가 가장 중요한 포인트라고 생각하는데,

    1. subproblem을 어떻게 만들것인가? (90%)

    2. 이걸 어떤 형식으로 저장하고, 사용할 것인가? (10%)

     

    subproblem을 어떻게 만들것인가를 아는 순간 2번은 자연스럽게 나올것이라고 생각된다.

     

    다이나믹 프로그래밍을 다루는(?) 2방식이 있는데,

    첫번째는 "Bottom to Top" 으로 일단 inital부터 내가 구하는 값까지의 데이터를 다 저장한 뒤, 꺼내쓰는 방법

    두번째는 "Top to Bottom(memoized)"으로 재귀를 통해 값들을 채워나가는 방법 

     

    아마 두번째가 더 많이 쓰이는 것 같기는 한데, 처음 다이나믹 프로그래밍을 다룰때 빠른 이해는 1번. 조금 익숙해지면 2번이 더 간결하고 직관적인 느낌적인 느낌느낌.

     

    아무튼 대표적인 Dynamic programming 문제 ROD cutting 과 LCS를 각각 2가지 방식으로 풀어보았다.

    문제 : ROD cutting 

    나무길이와 나무 길이당 가격이 주어질때 나무를 어떻게 잘라야 가장 높은 가격을 받을 수 있는가?

     

    생각해보면 엄청 간단한 subproblem이 보인다.

    P에 0부터 충분히 큰수까지의 길이당 price가 있다고 가정

    예를들어 ROD(4)를 구한다면,

    max( P[1]+ROD(3),P[2]+ROD(2),P[3]+ROD(1),P[4])

    P는 바로 구할수 있는거고, 만약 4보다 작은 ROD(n)의 데이터가 있다면, 바로 꺼내쓸수 있다.

     

    def b_to_u_rod(length,prices):
        r=[0]
        for i in range(1,length+1):
            q=0
            for j in range(1,i+1):
                q=max(q,prices[j]+r[i-j])
            r.append(q)
        return r[length]

    ROD bottom to top 

    r : 과거 데이터 저장

    for 구문에서 1부터 length까지 하나씩 돌면서, 해당 나무 길이에서의 최대 수익을 리스트 r 에 저장

     

    def t_to_d_rod(length,prices,r):
        if r[length]>=0:#값을 가지고 있다면
            return r[length]
        elif length==0:
            q=0
        else:#no value
            q=-10000
            for i in range(1,length+1):
                q=max(q,prices[i]+t_to_d_rod(length-i,prices,r))
        
        r[length]=q
        print(r)
        return q

    ROD memoized

    재귀방식으로 값을 불러오면서, 계산.

    데이터를 저장하는 리스트 r을 매개변수로 받으면서 업데이트

     

    문제 : LCS (Longest Common Subsequence)

    2개의 문자열, 공통의 subsequence 중 최장 subsequence 길이

    (솔직히 이건 좀 어려웠던 기억쓰.. subproblem으로 만드는데도 꽤 걸렸다. 그래서 하나 기억하고 있는건 뒤에서부터 맞추기)

     

    하 설명 너무 못하겠다...ㅠ

    예를들어 "ABC","BCAC"의 LCS구한다고 했을때,

    나는 맨 마지막 원소만 비교를 한다.

    만약에 맨마지막 원소가 같다면, 각 문자열의 마지막 원소를 제외한 문자열들의 LCS값+1이 LCS가 될것이다.

    LCS("ABC","BCAC")=1+LCS("AB","BCA")

    그리고 만약, 맨 마지막 원소가 다르다면, 문자열중 하나씩 마지막 원소를 뺏을때의 최대 LCS값이 LCS가 될것이다.

    LCS("AB","BCA")=max(LCS("AB","BC") , LCS("A","BCA"))

     

    이런식으로 가다보면, " "이되는 문자열을 만나는 순간 LCS값이 0이 될거고,

    이걸 시작으로 차근차근 저장하면,

    빠르게 LCS값을 구할수 있다는.. 방법..!

     

    저 장황한 설명을 그림으로 표현하면 요렇게!

    예를들어 표의(6,1)은 LCS("ABCBDA","B")를 구하는것으로 이해하면 된다.

    그러면 맨뒤의 원소들이 다르므로 max (LCS("ABCBD","B"),LCS("ABCBDA",""))값이고,

    이미 저장되어있는 표에는 LCS("ABCBD","B")=1 / LCS("ABCBDA","")=0이므로, LCS("ABCBDA","B")=1 이다.

     

    장황하다.. 됴르륵

     

    아무튼 Python으로 LCS풀이 구현

     

    def lcs1(X,Y):
        n=len(X)
        m=len(Y)
        T=[[0 for j in range(m+1)] for i in range(n+1)]
        for i in range(1,n+1):
            for j in range(1,m+1):
                if X[i-1]==Y[j-1]:
                    T[i][j]=1+T[i-1][j-1]
                else:
                    T[i][j]=max(T[i-1][j],T[i][j-1])
        
        return T[n][m]

    LCS Bottom to Top

    T 과거데이터 저장 2차원 리스트

    똑같이 for 문을 돌면서 T에 차곡차곡 쌓는다.

     

    def lcs2(X,Y,T):
        n=len(X)
        m=len(Y)
        if T[n][m]>=0: #값이 있다면
            return T[n][m]
        elif n==0 or m==0:
            T[n][m]=0
        else:
    
            if X[n-1]==Y[m-1]:
                T[n][m]=1+lcs2(X[:(n-1)],Y[:(m-1)],T)
            else:
                T[n][m]=max(lcs2(X[:(n-1)],Y[:m],T),lcs2(X[:n],Y[:(m-1)],T))
    
        return T[n][m]

    LCS memoized

     

     

     

    다이나믹 프로그래밍 관련 문제도 몇개 더 풀어야징🐥

     

    참고 | 

    https://www.youtube.com/watch?v=ASoaQq66foQ

    '알고리즘' 카테고리의 다른 글

    [DFS]1987  (0) 2020.11.05
    [DP]1915  (0) 2020.10.31
    sort 알고리즘  (0) 2020.06.22
    Divide and Conquer(1)  (0) 2020.06.17
    dijkstra 알고리즘  (0) 2020.06.11
Designed by Tistory.