상향식과 하향식의 차이점은 무엇입니까?


177

상향식 (동적 프로그래밍) 방법은 먼저 "작은"하위 문제를 찾고 구성하고 작은 문제의 해결책을 사용하여 큰 하위 문제를 해결한다.

하향식 (top-down)은 당신이 전에 하위 문제에 대한 해결책을 계산 한 경우 "자연으로"체크에서 문제 해결에 구성되어 있습니다.

조금 혼란 스러워요. 이 둘의 차이점은 무엇입니까?


답변:


247

rev4 : 사용자 Sammaron의 매우 설득력있는 설명은 아마도이 대답이 이전에 하향식과 상향식을 혼동한다고 언급했습니다. 원래이 답변 (rev3)과 다른 답변에서 "하단은 메모 화"( "하위 문제 가정")라고 말했지만, 그 반대 일 수 있습니다 (즉, "하향 문제"는 "하위 문제 가정"및 " 상향식 "은"하위 문제 구성 "일 수 있습니다. 이전에는 동적 프로그래밍의 하위 유형이 아닌 다른 종류의 동적 프로그래밍 인 메모에 대해 읽었습니다. 나는 그것을 구독하지 않았음에도 불구하고 그 견해를 인용했다. 나는이 답변이 문헌에서 적절한 참조를 찾을 수있을 때까지 용어에 대해 불가지론 적으로 다시 작성했다. 또한이 답변을 커뮤니티 위키로 변환했습니다. 학술 자료를 선호하십시오. 레퍼런스 목록:} {문학 : 5 }

요약

동적 프로그래밍은 중복 작업을 다시 계산하지 않는 방식으로 계산 순서를 정하는 것입니다. 주요 문제 (하위 문제 트리의 루트)와 하위 문제 (하위 트리)가 있습니다. 하위 문제는 일반적으로 반복되고 겹칩니다 .

예를 들어, 좋아하는 Fibonnaci의 예를 고려하십시오. 순진한 재귀 호출을 한 경우 이것은 하위 문제의 전체 트리입니다.

TOP of the tree
fib(4)
 fib(3)...................... + fib(2)
  fib(2)......... + fib(1)       fib(1)........... + fib(0)
   fib(1) + fib(0)   fib(1)       fib(1)              fib(0)
    fib(1)   fib(0)
BOTTOM of the tree

(다른 드문 문제에서이 트리는 일부 분기에서 무한 종료되어 비 종료를 나타낼 수 있으므로 트리의 맨 아래가 무한대로 커질 수 있습니다. 또한 일부 문제에서는 전체 트리가 어떻게 보이는지 알 수 없습니다. 따라서 공개 할 하위 문제를 결정하기위한 전략 / 알고리즘이 필요할 수 있습니다.)


메모, 표

상호 배타적이지 않은 동적 프로그래밍에는 적어도 두 가지 주요 기술이 있습니다.

  • 메모 화-이것은 laissez-faire 접근법입니다. 이미 모든 하위 문제를 계산했으며 최적의 평가 순서가 무엇인지 모를 것으로 가정합니다. 일반적으로 루트에서 재귀 호출 (또는 반복되는 동등 항목)을 수행하고 최적의 평가 순서에 가까워 지거나 최적의 평가 순서에 도달하는 데 도움이된다는 증거를 얻습니다. 결과 를 캐시 하므로 재귀 호출이 하위 문제를 다시 계산하지 않도록 하여 중복 하위 트리가 다시 계산되지 않도록해야합니다.

    • 예 : 피보나치 수열을 계산하는 경우 fib(100)이를 fib(100)=fib(99)+fib(98)호출 fib(99)=fib(98)+fib(97)하면,, ... 등 ..., fib(2)=fib(1)+fib(0)=1+0=1. 그런 다음 마침내 해결 fib(3)=fib(2)+fib(1)되지만 fib(2)캐시했기 때문에 다시 계산할 필요가 없습니다 .
    • 이것은 트리의 맨 위에서 시작하여 잎 / 하위 트리의 하위 문제를 다시 루트로 평가합니다.
  • 도표-동적 프로그래밍을 "표 작성"알고리즘으로 생각할 수도 있습니다 (일반적으로 다차원이지만이 '표'는 매우 드문 경우에 비 유클리드 기하학을 가질 수 있습니다 *). 이것은 메모 작성과 비슷하지만 한 단계 더 진행됩니다. 계산을 수행 할 정확한 순서를 미리 선택해야합니다. 이것은 순서가 정적 인 것이 아니라 메모보다 훨씬 더 유연하다는 것을 의미하지는 않습니다.

    • 예를 들면 : 당신이 피보나치를 수행하는 경우,이 순서대로 숫자를 계산하도록 선택할 수 있습니다 : fib(2), fib(3), fib(4)... 더 쉽게 다음 사람을 계산할 수 있도록 모든 값을 캐싱. 또한 테이블을 채우는 것으로 생각할 수도 있습니다 (다른 형태의 캐싱).
    • 나는 개인적으로 '표'라는 단어를 많이 듣지 않지만 매우 괜찮은 용어입니다. 어떤 사람들은 이것을 "동적 프로그래밍"이라고 생각합니다.
    • 알고리즘을 실행하기 전에 프로그래머는 전체 트리를 고려한 다음 루트를 향한 특정 순서로 하위 문제를 평가하는 알고리즘을 작성합니다. 일반적으로 테이블을 채 웁니다.
    • * 각주 : 때때로 '테이블'은 그리드와 같은 연결성을 가진 직사각형 테이블이 아닙니다. 오히려 나무와 같은 더 복잡한 구조 또는 문제 영역에 특정한 구조 (예 :지도에서 비행 거리 내에있는 도시) 또는 격자 모양의 격자가없는 격자 다이어그램을 가질 수 있습니다. 예를 들어, user3290797 은 트리에서 최대 독립 세트 를 찾는 동적 프로그래밍 예제를 연결했습니다 . 이는 트리에서 공백을 채우는 것에 해당합니다.

그것은 "동적 프로그래밍"패러다임에서, 가장 일반적인의에서 (나는 프로그래머가 전체 트리를 고려 말할 것이다 다음원하는 모든 속성 (일반적으로 시간 복잡성 및 공간 복잡성 조합)을 최적화 할 수있는 하위 문제 평가 전략을 구현하는 알고리즘을 작성합니다. 전략은 특정 하위 문제와 함께 어딘가에서 시작해야하며 해당 평가 결과에 따라 자체적으로 적용될 수 있습니다. "동적 프로그래밍"의 일반적인 의미에서 이러한 하위 문제를 캐시하려고 시도 할 수 있으며,보다 일반적으로 다양한 데이터 구조의 그래프와 같이 미묘한 차이로 하위 문제를 다시 방문하지 않도록하십시오. 종종 이러한 데이터 구조는 배열이나 테이블과 같은 핵심 요소입니다. 더 이상 필요하지 않은 하위 문제에 대한 솔루션을 버릴 수 있습니다.)

[이전에,이 답변은 하향식 용어와 상향식 용어에 대한 진술을했다; 메모와 표라고하는 두 가지 주요 접근 방식이 있습니다 (모두는 아니지만). 대부분의 사람들이 사용하는 일반적인 용어는 여전히 "동적 프로그래밍"이고 일부 사람들은 "동적 프로그래밍"의 특정 하위 유형을 지칭하기 위해 "Memoization"이라고 말합니다. 이 답변은 커뮤니티가 학술 논문에서 적절한 참조를 찾을 때까지 하향식과 상향식 중 어느 것을 말하는지는 거부합니다. 궁극적으로 용어보다는 구별을 이해하는 것이 중요합니다.]


장점과 단점

코딩의 용이성

Memoization은 코딩이 매우 쉽고 (일반적으로 * 자동으로이를 수행하는 "memoizer"주석 또는 래퍼 함수를 ​​작성할 수 있음) 첫 번째 접근 방식이되어야합니다. 표의 단점은 주문을해야한다는 것입니다.

누군가가 이미 컴파일 쓴 경우, 예를 들어 ... 함수를 직접 작성, 및 / 또는 불순한 / 비 기능 프로그래밍 언어로 코딩하는 경우 * (이것은 단지 쉽게 실제로 fib기능을 반드시 자체에 대한 재귀 호출을하고, 재귀 호출이 새로운 메모 함수를 호출하는지 확인하지 않고 함수를 마술로 메모 할 수는 없습니다.

재귀

하향식과 상향식은 모두 자연스럽지 않지만 재귀 또는 반복 테이블 채우기로 구현할 수 있습니다.

실제적인 관심사

메모를 사용하면 트리가 매우 깊을 경우 (예 :) fib(10^6)지연된 각 계산을 스택에 배치해야하고 스택 중 10 ^ 6을 갖기 때문에 스택 공간이 부족합니다.

최적

하위 문제를 방문하거나 방문하려는 순서가 최적이 아닌 경우 특히 하위 문제를 계산하는 방법이 두 가지 이상인 경우 (일반적으로 캐싱이 문제를 해결할 수 있지만 이론적으로는 캐싱이 가능할 수 있습니다) 이국적인 경우는 아닙니다). Memoization은 일반적으로 시간 복잡성을 공간 복잡성에 추가합니다 (예 : Fib로 테이블을 사용하면 O (1) 공간을 사용할 수 있지만 Fib를 사용한 메모는 O (N)을 사용하는 것처럼 계산을 버릴 수있는 자유가 더 많습니다. 스택 공간).

고급 최적화

또한 매우 복잡한 문제를 겪고 있다면 표를 작성하는 것 외에는 선택의 여지가 없을 수도 있습니다 (또는 메모를 원하는 곳에서 조정하는 데 더 적극적인 역할을 수행해야 함). 또한 최적화가 절대적으로 중요하고 최적화해야하는 상황에있는 경우 표를 사용하면 메모를 통해 제정신이되지 않는 최적화를 수행 할 수 있습니다. 겸손한 의견으로는 일반적인 소프트웨어 엔지니어링에서는이 두 경우 중 어느 것도 나오지 않기 때문에 (스택 공간과 같은) 무언가가 테이블을 만들 필요가 없다면 메모 ( "답을 캐시하는 기능")를 사용하는 것입니다. 기술적으로 스택 분출을 피하기 위해 1) 스택 크기 제한을 허용하는 언어로 스택 크기 제한을 늘리거나 2) 스택을 가상화하기 위해 지속적인 추가 작업을 수행 할 수 있습니다 (ick).


더 복잡한 예

여기에서는 일반적인 DP 문제 일뿐만 아니라 메모와 표를 흥미롭게 구분하는 특별한 관심사를 보여줍니다. 예를 들어, 한 공식이 다른 공식보다 훨씬 쉬워 지거나 기본적으로 테이블이 필요한 최적화가있을 수 있습니다.

  • 편집 거리를 계산하는 알고리즘 [ 4 ], 2 차원 테이블 채우기 알고리즘의 사소한 예로서 흥미로운

3
@ coder000001 : 파이썬 예제의 경우 구글 검색 python memoization decorator; 일부 언어에서는 메모 패턴을 캡슐화하는 매크로 나 코드를 작성할 수 있습니다. memoization 패턴은 "함수를 호출하는 것보다 캐시에서 값을 찾는 것 (값이 없으면 계산하여 캐시에 먼저 추가)"에 지나지 않습니다.
ninjagecko

16
나는 이것을 언급 한 사람을 보지 못했지만 Top down의 또 다른 장점은 조회 테이블 / 캐시를 드물게 빌드한다는 것입니다. (즉, 실제로 필요한 값을 입력하십시오). 따라서 이것은 쉬운 코딩 외에도 전문가 일 수 있습니다. 즉, 하향식은 모든 것을 계산하지 않기 때문에 실제 실행 시간을 절약 할 수 있습니다 (실행 시간은 엄청나게 향상되지만 점근 적 실행 시간은 동일 할 수 있음). 그러나 추가 스택 프레임을 유지하기 위해서는 추가 메모리가 필요합니다 (다시 말해서 메모리 소비 'may'는 두 배이지만 무조건 동일합니다)
InformedA

2
하위 문제가 중복되는 캐시 솔루션에 대한 하향식 접근 방식이 memoization 이라는 기술이라는 인상을 받고 있습니다. 또한 테이블을 채우고 상향식 방식은 중복을 재 계산하는 하위 문제라고 피한다 . 동적 프로그래밍을 사용할 때 이러한 기술을 사용할 수 있는데, 이는 훨씬 큰 문제를 해결하기 위해 하위 문제를 해결하는 것을 말합니다. 이것은 여러 곳에서 대신 동적 프로그래밍을 사용하는이 답변과 모순되는 것처럼 보입니다 . 누가 맞습니까?
Sammaron

1
@Sammaron : 흠, 당신은 좋은 지적을합니다. Wikipedia에서 내 출처를 확인했을 것입니다. cstheory.stackexchange를 약간 확인하면 "아래쪽"이 바닥이 미리 알려져 있음을 암시한다는 데 동의합니다. 나는 모호한 용어를 발견했을 때 이중 관점에서 문구를 해석했다 ( "하단"은 하위 문제에 대한 해결책을 가정하고 암기한다. 편집에서이 문제를 해결하려고합니다.
ninjagecko

1
@mgiuffrida : 스택 공간은 때때로 프로그래밍 언어에 따라 다르게 취급됩니다. 예를 들어 python에서 메모 된 재귀 fib를 수행하려고하면 실패합니다 fib(513). 내가 느끼는 과부하 용어는 여기에 있습니다. 1) 더 이상 필요없는 하위 문제를 항상 버릴 수 있습니다. 2) 불필요하게 하위 문제를 계산하지 않아도됩니다. 3) 1과 2는 명시적인 데이터 구조가 없으면 하위 문제를 저장하기 위해 코딩하기가 훨씬 어려울 수 있습니다.
ninjagecko

76

하향식 DP와 상향식 DP는 동일한 문제를 해결하는 두 가지 방법입니다. 피보나치 수 계산에 대한 메모 화 된 (위에서 아래로) vs 동적 (아래에서 위로) 프로그래밍 솔루션을 고려하십시오.

fib_cache = {}

def memo_fib(n):
  global fib_cache
  if n == 0 or n == 1:
     return 1
  if n in fib_cache:
     return fib_cache[n]
  ret = memo_fib(n - 1) + memo_fib(n - 2)
  fib_cache[n] = ret
  return ret

def dp_fib(n):
   partial_answers = [1, 1]
   while len(partial_answers) <= n:
     partial_answers.append(partial_answers[-1] + partial_answers[-2])
   return partial_answers[n]

print memo_fib(5), dp_fib(5)

개인적으로 메모가 훨씬 더 자연 스럽습니다. 재귀 함수를 사용하고 기계적인 프로세스로 캐시 할 수 있습니다 (캐시에서 먼저 조회 응답을 반환하고 가능하면 반환하십시오. 그렇지 않으면 재귀 적으로 계산 한 다음 반환하기 전에 나중에 사용할 수 있도록 캐시에 계산을 저장하십시오). 동적 프로그래밍을 위해서는 솔루션이 계산되는 순서를 인코딩해야합니다. 따라서 작은 문제가 발생하기 전에 "큰 문제"가 계산되지 않습니다.


1
아, 이제 "상하"와 "하단"의 의미를 알 수 있습니다. 실제로 메모 화 대 DP를 참조하는 것입니다. 그리고 제목에서 DP를 언급하기 위해 질문을 편집 한 사람이라고 생각합니다.
ninjagecko

memoized fib v / s normal recursive fib의 런타임은 무엇입니까?
Siddhartha

정상적인 coz의 지수 (2 ^ n)는 재귀 트리라고 생각합니다.
Siddhartha

1
그래, 선형이야! 재귀 트리를 꺼내서 어떤 호출을 피할 수 있는지 확인하고 memo_fib (n-2) 호출을 처음 호출 한 후에는 피할 수 있다는 것을 깨달았습니다. 선형으로 줄어 듭니다.
Siddhartha

1
DP는 본질적으로 각 결과가 최대 한 번 계산되는 결과 테이블을 작성하는 것을 포함하므로 DP 알고리즘의 런타임을 시각화하는 간단한 방법은 테이블의 크기를 보는 것입니다. 이 경우 크기는 n (입력 값당 하나의 결과)이므로 O (n)입니다. 다른 경우에, 그것은 n ^ 2 행렬 일 수 있으며, 그 결과 O (n ^ 2) 등이 발생합니다.
Johnson Wong

22

동적 프로그래밍의 주요 특징은 중복되는 하위 문제가 있다는 것 입니다. 즉, 해결하려는 문제가 하위 문제로 분류 될 수 있으며 이러한 하위 문제 중 많은 부분이 하위 하위 문제를 공유합니다. 그것은 "분열과 정복"과 같지만 여러 번 같은 일을하게됩니다. 2003 년부터 이러한 문제를 가르치거나 설명 할 때 사용한 예 : 피보나치 수를 재귀 적으로 계산할 수 있습니다 .

def fib(n):
  if n < 2:
    return n
  return fib(n-1) + fib(n-2)

자주 사용하는 언어를 사용하여 실행 해보십시오 fib(50). 매우 오랜 시간이 걸립니다. fib(50)그 자체 만큼이나 많은 시간 ! 그러나 많은 불필요한 작업이 수행되고 있습니다. fib(50)호출 fib(49)하고 fib(48), 그러나 그 모두는 호출 끝날 fib(47)값이 동일 할지라도. 사실, fib(47)세 번 계산됩니다에서 직접 호출에 의해 fib(49), 직접 호출에서 fib(48), 또한 서로 직접 호출에 의해 fib(48),의 계산에 의해 산란 된 사람은 fib(49)당신이 볼 그래서 ... 우리는이 중복 하위 문제를 .

좋은 소식 : 같은 값을 여러 번 계산할 필요가 없습니다. 한 번 계산하면 결과를 캐시하고 다음에 캐시 된 값을 사용하십시오! 이것이 동적 프로그래밍의 본질입니다. "하향식", "기억"또는 기타 원하는 것을 호출 할 수 있습니다. 이 접근 방식은 매우 직관적이며 구현하기가 매우 쉽습니다. 재귀 솔루션을 먼저 작성하고 소규모 테스트에서 테스트하고 메모 (이미 계산 된 값의 캐싱)를 추가하고 --- bingo! --- 끝났습니다.

일반적으로 재귀없이 상향식으로 작동하는 동등한 반복 프로그램을 작성할 수도 있습니다. 이 경우 이것은보다 자연스러운 접근 방법입니다. 1에서 50까지 반복하여 피보나치 수를 계산하십시오.

fib[0] = 0
fib[1] = 1
for i in range(48):
  fib[i+2] = fib[i] + fib[i+1]

흥미로운 시나리오에서 상향식 솔루션은 일반적으로 이해하기가 더 어렵습니다. 그러나 일단 이해하면 일반적으로 알고리즘 작동 방식을 훨씬 명확하게 알 수 있습니다. 실제로, 사소한 문제를 해결할 때는 먼저 하향식 접근 방식을 작성하고 작은 예제에서 테스트하는 것이 좋습니다. 그런 다음 상향식 솔루션을 작성하고 두 솔루션을 비교하여 동일한 결과를 얻도록하십시오. 이상적으로 두 솔루션을 자동으로 비교하십시오. 이상적으로 많은 테스트를 생성하는 작은 루틴을 작성 하십시오.특정 크기까지 작은 테스트 --- 두 솔루션이 동일한 결과를 제공하는지 확인합니다. 그런 다음 프로덕션 환경에서 상향식 솔루션을 사용하지만 맨 아래 코드를 유지하십시오. 이렇게하면 다른 개발자가 자신이하는 일을 더 쉽게 이해할 수 있습니다. 상향식 코드는 이해하기 어렵고 심지어 작성했거나 자신이하는 일을 정확히 아는 경우에도 이해할 수 없습니다.

많은 애플리케이션에서 상향식 접근 방식은 재귀 호출의 오버 헤드로 인해 약간 더 빠릅니다. 스택 오버플로는 특정 문제에서 문제가 될 수 있으며 이는 입력 데이터에 따라 크게 달라질 수 있습니다. 동적 프로그래밍을 충분히 이해하지 못하면 스택 오버플로를 유발하는 테스트를 작성하지 못할 수도 있지만 언젠가는 여전히 이런 일이 발생할 수 있습니다.

문제 공간이 너무 커서 모든 하위 문제를 해결할 수 없기 때문에 하향식 접근 방식이 유일하게 실현 가능한 솔루션 인 문제가 있습니다. 그러나 "캐싱"은 입력에 해결해야 할 하위 문제의 일부만 필요하기 때문에 여전히 합리적인 시간에 작동합니다. 그러나 명시 적으로 정의하고 해결해야하는 하위 문제를 정의하기에는 너무 까다로워서 솔루션. 반면에 모든 하위 문제 를 해결해야 할 상황이 있습니다 . 이 경우 계속해서 상향식을 사용하십시오.

필자는 개인적으로 Word 랩 최적화 문제 로 단락 최적화를 위해 맨 아래를 사용하고 싶습니다 (Knuth-Plass 줄 바꿈 알고리즘을 찾으십시오. 적어도 TeX는 그것을 사용하고 Adobe Systems의 일부 소프트웨어는 비슷한 접근 방식을 사용합니다). 고속 푸리에 변환에 상향식을 사용 합니다.


여보세요!!! 다음 제안이 올바른지 확인하고 싶습니다. -동적 프로그래밍 알고리즘의 경우 상향식으로 모든 값을 계산하는 것이 재귀 및 메모 사용보다 훨씬 빠릅니다. -동적 알고리즘의 시간은 항상 Ο (Ρ)이며 여기서 Ρ는 하위 문제의 수입니다. -NP의 각 문제는 기하 급수적으로 해결할 수 있습니다.
Mary Star

위의 제안에 대해 무엇을 말할 수 있습니까? 당신은 아이디어가 있습니까? @osa
Mary Star

@evinda, (1) 항상 잘못되었습니다. 동일하거나 점증 적으로 느리다 (모든 하위 문제가 필요하지 않은 경우 재귀가 더 빠를 수 있음). (2)는 O (1)의 모든 하위 문제를 해결할 수있는 경우에만 옳습니다. (3) 옳다. NP의 각 문제는 비 결정적 기계 (양자 컴퓨터와 같이 여러 작업을 동시에 수행 할 수있는 케이크와 케이크를 동시에 먹고 두 결과를 모두 추적 할 수있는)에서 다항식 시간으로 해결할 수 있습니다. 어떤 의미에서 NP의 각 문제는 일반 컴퓨터에서 기하 급수적으로 해결할 수 있습니다. 참고 사항 : P의 모든 것은 NP에도 있습니다. 예 : 2 개의 정수 추가
osa

19

피보나치 시리즈를 예로 들어 보겠습니다.

1,1,2,3,5,8,13,21....

first number: 1
Second number: 1
Third Number: 2

그것을 넣는 또 다른 방법은

Bottom(first) number: 1
Top (Eighth) number on the given sequence: 21

처음 다섯 피보나치 수의 경우

Bottom(first) number :1
Top (fifth) number: 5 

이제 재귀 피보나치 시리즈 알고리즘을 예로 들어 보겠습니다.

public int rcursive(int n) {
    if ((n == 1) || (n == 2)) {
        return 1;
    } else {
        return rcursive(n - 1) + rcursive(n - 2);
    }
}

다음 명령으로이 프로그램을 실행하면

rcursive(5);

알고리즘을 자세히 살펴보면 다섯 번째 숫자를 생성하기 위해서는 세 번째와 네 번째 숫자가 필요합니다. 따라서 내 재귀는 실제로 top (5)에서 시작하여 아래쪽 / 낮은 숫자로갑니다. 이 접근법은 실제로 하향식 접근법입니다.

동일한 계산을 여러 번 수행하지 않도록 동적 프로그래밍 기술을 사용합니다. 이전에 계산 된 값을 저장하고 재사용합니다. 이 기술을 메모라고합니다. 현재 문제를 논의하는 데 필요하지 않은 메모 작성 이외의 동적 프로그래밍에는 더 많은 것이 있습니다.

위에서 아래로

원래 알고리즘을 다시 작성하고 메모 된 기술을 추가 할 수 있습니다.

public int memoized(int n, int[] memo) {
    if (n <= 2) {
        return 1;
    } else if (memo[n] != -1) {
        return memo[n];
    } else {
        memo[n] = memoized(n - 1, memo) + memoized(n - 2, memo);
    }
    return memo[n];
}

그리고 우리는이 방법을 다음과 같이 실행합니다

   int n = 5;
    int[] memo = new int[n + 1];
    Arrays.fill(memo, -1);
    memoized(n, memo);

이 솔루션은 알고리즘이 최상위 값에서 시작하여 각 단계의 맨 아래로 이동하여 최상위 값을 얻음에 따라 여전히 하향식입니다.

상향식

그러나 문제는 첫 번째 피보나치 수에서와 같이 바닥에서 시작하여 위로 올라갈 수 있다는 것입니다. 이 기술을 사용하여 다시 작성하겠습니다.

public int dp(int n) {
    int[] output = new int[n + 1];
    output[1] = 1;
    output[2] = 1;
    for (int i = 3; i <= n; i++) {
        output[i] = output[i - 1] + output[i - 2];
    }
    return output[n];
}

이제이 알고리즘을 살펴보면 실제로 낮은 값에서 시작하여 맨 위로 이동합니다. 5 번째 피보나치 수가 필요한 경우 실제로 1 번째를 계산하고 두 번째로 5 번째 숫자까지 세 번째로 계산합니다. 이 기술을 실제로 상향식 기술이라고합니다.

마지막 두 알고리즘은 동적 프로그래밍 요구 사항을 완전히 채 웁니다. 그러나 하나는 하향식이고 다른 하나는 상향식입니다. 두 알고리즘 모두 비슷한 공간 및 시간 복잡성을 가지고 있습니다.


상향식 접근 방식이 종종 비 재귀 방식으로 구현된다고 말할 수 있습니까?
Lewis Chan

아니, 모든 루프 논리를 재귀로 변환 할 수있다
Ashvin Sharma

3

다이나믹 프로그래밍은 종종 Memoization이라고합니다!

1. 메모 화는 하향식 기술 (주어진 문제를 분해하여 시작)이고 동적 프로그래밍은 상향식 기술 (사소한 하위 문제에서 주어진 문제를 향한 시작)

2. DP는 기본 사례에서 시작하여 솔루션을 찾고 위로 진행합니다. DP는 모든 하위 문제를 해결합니다.

필요한 하위 문제 만 해결하는 Memoization과 달리

  1. DP는 지수 시간 무차별 대입 솔루션을 다항식 시간 알고리즘으로 변환 할 가능성이 있습니다.

  2. DP는 반복적이므로 훨씬 더 효율적일 수 있습니다.

반대로, 메모 화는 재귀로 인한 오버 헤드에 대한 비용을 지불해야합니다.

더 간단하게, Memoization은 하향식 접근 방식을 사용하여 문제를 해결합니다. 즉 핵심 (주요) 문제로 시작한 다음 하위 문제로 나누고 이러한 하위 문제를 유사하게 해결합니다. 이 방법에서는 동일한 하위 문제가 여러 번 발생하고 더 많은 CPU주기를 소비 할 수 있으므로 시간 복잡성이 증가합니다. 동적 프로그래밍에서 동일한 하위 문제는 여러 번 해결되지 않지만 이전 결과는 솔루션을 최적화하는 데 사용됩니다.


4
사실은 아닙니다. 메모리 화는 캐시를 사용하여 DP와 같은 시간 복잡성을 줄일 수 있습니다
InformedA

3

간단히 하향식 접근 방식은 하위 문제를 반복해서 호출하기 위해 재귀를 사용합니다
. 상향식 접근 방식은 단일 호출 방식을 사용하지 않고 단일을 사용하므로 더 효율적입니다.


1

아래는 하향식 거리 편집 문제에 대한 DP 기반 솔루션입니다. 동적 프로그래밍의 세계를 이해하는 데 도움이되기를 바랍니다.

public int minDistance(String word1, String word2) {//Standard dynamic programming puzzle.
         int m = word2.length();
            int n = word1.length();


     if(m == 0) // Cannot miss the corner cases !
                return n;
        if(n == 0)
            return m;
        int[][] DP = new int[n + 1][m + 1];

        for(int j =1 ; j <= m; j++) {
            DP[0][j] = j;
        }
        for(int i =1 ; i <= n; i++) {
            DP[i][0] = i;
        }

        for(int i =1 ; i <= n; i++) {
            for(int j =1 ; j <= m; j++) {
                if(word1.charAt(i - 1) == word2.charAt(j - 1))
                    DP[i][j] = DP[i-1][j-1];
                else
                DP[i][j] = Math.min(Math.min(DP[i-1][j], DP[i][j-1]), DP[i-1][j-1]) + 1; // Main idea is this.
            }
        }

        return DP[n][m];
}

집에서 재귀 구현을 생각할 수 있습니다. 이전에 이와 같은 문제를 해결하지 않았다면 상당히 좋고 도전적입니다.


1

하향식 : 지금까지 계산 된 값을 추적하고 기본 조건이 충족되면 결과를 반환합니다.

int n = 5;
fibTopDown(1, 1, 2, n);

private int fibTopDown(int i, int j, int count, int n) {
    if (count > n) return 1;
    if (count == n) return i + j;
    return fibTopDown(j, i + j, count + 1, n);
}

상향식 : 현재 결과는 하위 문제의 결과에 따라 다릅니다.

int n = 5;
fibBottomUp(n);

private int fibBottomUp(int n) {
    if (n <= 1) return 1;
    return fibBottomUp(n - 1) + fibBottomUp(n - 2);
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.