동적 계획: Swift 실현

중심 사상
동적 기획 (dynamic programming, 약칭 dp) 은 분할 방법 에 비해 다음 과 같다.
유사 점 은 모두 조합 자 문제 의 해 를 통 해 원 문 제 를 해결 하 는 것 이다.차이 점 은 분 치 방법 으로 문 제 를 서로 교차 하지 않 는 서브 문제 로 나 누고 재 귀적 인 해결 서브 문제 로 나 누 는 것 이다.한편, 동태 계획 은 서브 문제 가 중첩 되 는 상황 에 있 고 서로 다른 서브 문제 의 해 제 는 반복 적 으로 진행 되 며 공공 서브 문 제 를 반복 적 으로 해결 하 는 것 이다.
동적 계획 의 주 제 는 모든 하위 문제 에 대해 한 번 만 풀 고 기록 하 는 것 이다.동적 계획 의 응용 장면 은 최적화 문제 (optimization problem) 를 해결 하 는 데 있다.
동적 계획 알고리즘 절차:
  • 가장 좋 은 구조 적 특징 을 묘사
  • 재 귀적 정의 최 적 화
  • 가장 좋 은 값 을 계산 하 는데 여 기 는 보통 아래 에서 위로 올 라 가 는 방법
  • 을 사용한다.
  • 계 산 된 정보 구 조 를 이용 하여 최 적 화 된 해
  • 밤 을 들다
    밤 1) 강철 막대 절단
    입력 원: 강철 막대 길이 n 및 각 사이즈 의 강철 막대 단가 pi 목표: 가격 rn 최대
    사고의 방향
    먼저 두 절 (특수 한 두 절 에 속 하지 않 음) 의 가장 좋 은 절단 수익 을 고려 합 니 다: rn = max (pn, r1 + rn - 1 + r2 + rn - 2,..., rn - 1 + r1)
    상식 은 반증 법 으로 확실히 이때 최대 치 를 얻 었 다 는 것 을 증명 할 수 있다.
    두 절 단 된 두 개의 강철 막대 조합 은 최 적 화 된 서브 구조 (optional substructure) 의 성질 을 만족 시 킵 니 다. 문 제 는 관련 서브 문제 의 최 적 화 된 조합 으로 이 루어 져 있 고 이런 서브 문 제 는 독립 적 으로 해결 할 수 있 습 니 다.
    이런 사고방식 의 이 곡 동공 의 변종 은 왼쪽 (오른쪽 도 마음대로) 에서 길이 가 i 인 한 단락 을 잘라 내 고 n - i 의 한 단락 만 계속 절단 (귀속) 하고 왼쪽 은 절단 하지 않 아 도 된다 는 것 이다.이러한 장점 은 매우 뚜렷 하 다. 한 단계 에서 한 방향 만 재 귀적 으로 호출 하고 대충 말 하면 절반 의 함수 호출 을 줄 일 수 있다.문 제 는 이렇게 설명 할 수 있다.
    첫 번 째 단락 의 길 이 는 n 이 고 수익 은 pn 이 며 나머지 부분의 길 이 는 0 (왼쪽, 절단 하지 않 음) 이 며 대응 하 는 수익 은 r0 이 며 새로운 공식 을 얻 을 수 있다. rn = max (pi + rn - i) (1 < = i < = n)
    위 에서 아래로 순환 하여 실현 하 다.
    의사 코드 는 다음 과 같 습 니 다:
    CUT-ROD(p, n)
    if n == 0 
        return 0
    q = -∞
    for i = 1 to n
        q = max(q, p[i] + CUT-ROD(p, n-i))
    return q
    

    상술 한 알고리즘 효율 은 입력 규모 가 클 때 찌꺼기 로 변 한다. 원인 은 매우 간단 하 다. 컴퓨터 는 이미 계 산 된 문 제 를 끊임없이 반복 해서 계산한다.
    동적 계획 을 사용 하여 해답 을 구하 다
    주 제 는 공간 교환 시간, 시공 저울질 (time - memory trade - of) 이다.
  • 메모 가 있 는 위 에서 아래로 법 (top - 다운 with memoization).자 연 스 럽 게 (정상 인의 생각, 큰 문제 가 작 아 지 는 문제) 작 성 됩 니 다.모든 과정 에서 하위 문제 의 해 를 저장 합 니 다.풀 리 는 과정 에서 이 문제 가 이미 풀 렸 는 지 아 닌 지 를 판단 하 다.
  • 아래 에서 위로 법 (bottom - up method).하위 문 제 를 규모 에 따라 정렬 하고 작은 것 에서 큰 것 으로 순서대로 풀다.어떤 하위 문 제 를 풀 때 더 작은 문제 에 대한 해답 이 끝 났 을 때 바로 사용 할 수 있 습 니 다.

  • 위 에서 아래로 의사 코드:
    MEMOIZED-CUT-ROD(p, n)
    let r[0..n] be a new array
    for i = 0 to n
        r[i] = -∞
    return MEMOIZED-CUT-ROD-AUX(p, n, r)
    
    MEMOIZED-CUT-ROD-AUX(p, n, r)
    if r[n] >= 0
        return r[n]
    if n == 0
        q = 0
    else q = -∞
        for i = 1 to n
            q = max(q, p[i] + MEMOIZED-CUT-ROD-AUX(p, n-i, r))
    r[n] = q
    return q            
    

    아래 에서 위로 의사 코드:
    BOTTOM-UP-CUT-ROD(p, n)
    let r[0..n] be a new array
    r[0] = 0
    
    for j = 1 to n
        q = -∞
        for i = 1 to j
            q = max(q, p[i] + r[j-i])
        r[j] = q
    return r[n]
    

    재 구성 해
    확장 버 전, 가장 좋 은 절단 방법 저장
    EXTENDED-BOTTOM-UP-CUT-ROD(p, n)
    let r[0..n] and s[0..n] be new array
    r[0] = 0
    for j = 1 to n
        q = -∞
        for i = 1 to j
            if q < p[i] + r[j-i]
                q = p[i] + r[j-i]
                s[j] = i
        r[j] = q
    return r and s
    
    PRINT-CUT-ROD-SOLUTION(p, n)
    (r, s) = EXTENDED-BOTTOM-UP-CUT-ROD(p, n)
    while n > n
        print s[n]
        n = n - s[n]
    

    Swift 실현
    주: 아래 에서 위로 정렬 해 야 합 니 다. 본 밤 에 가격 은 사이즈 의 길이 와 정비례 하기 때문에 이미 비 체감 서열 입 니 다 ~
    func extendedBottomUpCutRod(priceList: [Int], rotLength: Int) -> (result: [Int], solution: [Int]) {
        var result = Array (count: rotLength + 1 , repeatedValue: 0 )
        var solution = Array (count: rotLength + 1 , repeatedValue: 0 )
    
        for var j = 1; j <= rotLength; ++j {
            var optNext = -10000
            for var i = 1; i <= j; i++ {
                if optNext < priceList[i] + result[j - i] {
                    optNext = priceList[i] + result[j - i]
                    solution[j] = i
                }
            }
    
            result[j] = optNext
        }
    
        return (result, solution)
    }
    
    func printCutRodSolution(priceList: [Int], var rotLength: Int) {
        var result: [Int]
        var solution: [Int]
        (result, solution) = extendedBottomUpCutRod(priceList, rotLength)
    
        while rotLength > 0 {
            println(solution[rotLength])
            rotLength = rotLength - solution[rotLength]
        }
    }
    
    var priceArray: [Int] = [0, 1, 5, 8, 9, 10, 17, 17, 20, 24, 30]
    
    printCutRodSolution(priceArray, 4)
    

    밤 2) 최 장 공공 서열
    입력 원본: 배열 X, 배열 Y 목표: 두 그룹의 최 장 공공 교차 집합 서브 배열 을 구하 십시오.
    최 장 공공 서브 시퀀스 문제 (longest - common - subsequence problem) 에 부 딪 혔 을 때, LCD 문 제 를 해결 하기 위해 dp 를 주저 하지 않 고 사용 할 수 있 습 니 다.
    사고의 방향
    DP 문제 단계 1: 가장 잘 풀 리 는 구조 적 특징 을 묘사 하고 폭력 적 으로 해결 하 는 효율 이 매우 무 섭 습 니 다. 만약 에 X 가 Y 보다 짧 고 길이 가 m 라면 그 하위 서열 은 2 ^ m 개 입 니 다.그 러 니까 2 ^ m 개 는 풀 수 있다 는 거 야.입력 배열 의 규모 가 크 면 자 연 스 럽 게 하하.
    우선 LCS 문제 가 최 우선 서브 구조 적 성격 을 갖 고 있 는 지 살 펴 본다.LCS 의 하위 문제 (소 규모) 는 자 연 스 럽 게 두 서열 의 접두사 입 니 다.
    각종 반증 법 은 LCS 문제 가 최 적 화 된 서브 구 조 를 갖 고 있다 는 것 을 증명 할 수 있 고 약 하 다.수학 하 는 거 아니 야.
    LCS 최 우수 서브 구조의 특징 은 다음 과 같다. 만약 에 X =, Y = 이 두 개의 서열 이 고 Z = X 와 Y 의 임 의 LCS 라면
  • 만약 xm = yn 이 라면 zk = xm = yn 과 Zk - 1 은 Xm - 1 과 Yn - 1 의 LCS 이다.
  • 만약 xm! =yn, 그럼 zk!xm 는 Z 가 Xm - 1 과 Y 의 LCS 라 는 것 을 의미한다.[비고: LCS 에 xm 가 포함 되 어 있 지 않 기 때문에 문제 규모 가 줄 어 들 고 X 시퀀스 의 구성원 은 xm 를 제거 합 니 다. 아래 는 같 습 니 다]
  • 만약 xm! =yn, 그럼 zk!yn 은 Z 가 X 와 Yn - 1 의 LCS 라 는 것 을 의미한다.

  • 끊 임 없 는 1, 2, 3 은 하나의 해 (재 귀 해) 를 얻 을 수 있다.
    DP 문제 절차 2: 재 귀 해 를 구 하 는 것 은 LCS 최 우수 서브 구조의 성질 에 따라 1, 2, 3 은 다음 과 같은 공식 (재 귀 해) 이 있 을 수 있다.
    DP 문제 절차 3: 2 차원 배열 c [0. m, 0. n] 를 밑 에서 위로 계산 하여 c [i, j] 의 해 를 저장 합 니 다.줄 의 주 순서 (즉, 외곽 for 순환 은 1 에서 m) 에 따라 보조 목록 b [1. m, 1. n] 가 구조 최 적 화 를 도 와 줘 야 합 니 다.b [i, j] 에 저 장 된 매 거 형 은 c [i, j] 를 계산 할 때의 하위 문 제 를 가장 잘 풀 수 있 습 니 다.c [m, n] 는 X 와 Y 의 LCS 길 이 를 저장 합 니 다.
    DP 문제 4 단계: 가장 좋 은 위조 코드 제시
    LCS-LENGTH(X, Y)
    m = X.length
    n = Y.length
    
    let b[1..m, 1..n] and c[0..m, 0..n] be new tables
    for i = 1 to m //   0     ,          
        c[i, 0] = 0
    for j = 0 to n
        c[0, j] = 0
    
    for i = 1 to m
        for j = 1 to n
            if xi == yi
                c[i, j] = c[i-1, j-1] + 1
                b[i, j] = left-top  // left-top   
            elseif c[i-1, j] >= c[i, j-1]
                c[i, j] = c[i-1, j]
                b[i, j] = top       // top   
            else 
                c[i, j] = c[i, j-1]
                b[i, j] = left      // left   
    
    return c and b           
    

    표 b 를 통 해 우 리 는 X 와 Y 를 추적 해 낸 LCS 를 신속하게 찾 아 볼 수 있다.LCS 위조 코드 인쇄 는 다음 과 같 습 니 다:
    PRINT-LCS(b, X, i, j) //      X,Y  
    if i == 0 or j == 0
        return
    if b[i, j] == left-top
        PRINT-LCS(b, X, i-1, j-1)
        print xi
    elseif b[i, j] = top
        PRINT-LCS(b, X, i-1, j)
    else 
        PRINT-LCS(b, X, i, j-1)
    

    알고리즘 개선
    실제로 우 리 는 상수 시간 내 에 c [i, j] 와 c [i - 1, j - 1], c [i - 1, j], c [i, j - 1] 의 관 계 를 판단 할 수 있다.그러므로 보조 표 b 가 전혀 필요 하지 않 을 수 있다.
    Swift 실현
    // c[i, j] c[i-1, j-1],c[i-1, j],c[i, j-1]         
    enum LCSDirection: Int {
        case left_top = 0
        case top = 1
        case left = 2
    }
    
    //     
    struct Matrix {
        let rows: Int, columns: Int
        var grid: [Int]
    
        init(rows: Int, columns: Int) {
            self.rows = rows
            self.columns = columns
            grid = Array(count: rows * columns, repeatedValue: -10000)
        }
    
        func indexIsValidForRow(row: Int, column: Int) -> Bool {
            return row >= 0 && row < rows && column >= 0 && column < columns
        }
    
        subscript(row: Int, column: Int) -> Int {
            get {
                assert(indexIsValidForRow(row, column: column), "Index out of range")
                return grid[(row * columns) + column]
            }
            set {
                assert(indexIsValidForRow(row, column: column), "Index out of range")
                grid[(row * columns) + column] = newValue
            }
        }
    }
    
    func LCSLength(var rowArray: [Int], var columnArray: [Int]) -> (b: Matrix, c: Matrix){
        var m = rowArray.count
        var n = columnArray.count
    
        var b: Matrix = Matrix(rows: m+1, columns: n+1)
        var c: Matrix = Matrix(rows: m+1, columns: n+1)
    
        for var i = 1; i <= m; i++ {
            c[i, 0] = 0
        }
    
        for var j = 0; j <= n; j++ {
            c[0, j] = 0
        }
    
    
        for var i = 1; i <= m; i++ {
            for var j = 1; j <= n; j++ {
                if rowArray[i-1] == columnArray[j-1] {
                    c[i, j] = c[i-1, j-1] + 1
                    b[i, j] = LCSDirection.left_top.rawValue
                } else if c[i-1, j] >= c[i, j-1] {
                    c[i, j] = c[i-1, j]
                    b[i, j] = LCSDirection.top.rawValue
                } else {
                    c[i, j] = c[i, j-1]
                    b[i, j] = LCSDirection.left.rawValue
                }
            }
        }
    
        return (b, c)
    }
    
    func printLCS(b: Matrix, rowArray: [Int], rowIndex: Int, columnIndex: Int) {
        if rowIndex == 0 || columnIndex == 0 { return }
    
        if b[rowIndex, columnIndex] == LCSDirection.left_top.rawValue {
            printLCS(b, rowArray, rowIndex-1, columnIndex-1)
            println(rowArray[rowIndex-1])
        } else if b[rowIndex, columnIndex] == LCSDirection.top.rawValue {
            printLCS(b, rowArray, rowIndex-1, columnIndex)
        } else {
            printLCS(b, rowArray, rowIndex, columnIndex-1)
        }
    }
    
    
    var A: [Int] = [1, 2, 3, 2, 4, 1, 2]
    var B: [Int] = [2, 4, 3, 1, 2, 1]
    
    var bRecord: Matrix
    var cCommon: Matrix
    
    (bRecord, cCommon) = LCSLength(A, B)
    printLCS(bRecord, A, A.count, B.count)
    

    밤 3) 가장 좋 은 두 갈래 검색 나무
    잇다

    좋은 웹페이지 즐겨찾기