FE RYAN
완벽하지 않으면 어때
FE RYAN
전체 방문자
오늘
어제

블로그 메뉴

  • 💾 깃허브 링크
  • 홈
  • 태그
  • 분류 전체보기 (151)
    • 개인프로젝트 (8)
      • 개인 포트폴리오 웹앱 (6)
      • 프론트엔드 기술면접 아카이빙 웹앱 (2)
    • 기록 (121)
      • 원티드 프리온보딩 인턴십 (0)
      • 코드스테이츠 프론트엔드 (75)
      • 생각들 (3)
      • Today I learned (32)
      • 회고 (9)
      • 리뷰 (1)
    • 개발 (17)
      • React (3)
      • Javascript (7)
      • CSS (1)
      • HTML (3)
      • HTTP (1)
      • 자료구조 (0)
      • 알고리즘 (2)
    • 코딩테스트 (2)
      • 백준 (2)
      • 프로그래머스 (0)
    • 디자인 (1)
      • UI & UX (1)
    • 수학 (0)
    • 자기계발 (0)

공지사항

인기 글

태그

  • 원시타입
  • seb 39
  • 딥다이브
  • 프론트엔드
  • 자바스크립트
  • 코드스테이츠
  • Til
  • 자바스크립트 딥다이브
  • 포트폴리오
  • 리액트
  • css
  • 메인프로젝트
  • 타입스크립트
  • HTML
  • 신입개발자
  • seb39
  • useMemo
  • 회고
  • 부트캠프
  • ES6

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
FE RYAN
개발/알고리즘

DP(동적 프로그래밍) 자바스크립트 구현/ 작성중

DP(동적 프로그래밍) 자바스크립트 구현/ 작성중
개발/알고리즘

DP(동적 프로그래밍) 자바스크립트 구현/ 작성중

2022. 6. 23. 22:12
728x90

동적 프로그래밍

Colt Steele 저자의 유데미 Javascript 자료구조 & 알고리즘 강의를 바탕으로 공부 내용을 정리합니다.

1. 정의

동적 프로그래밍은 복잡한 문제를 더 간단한 하위 문제들의 모음으로 분해하여 각각의 하위 문제들을 풀고 그 답을 저장하는 방식으로 문제를 푸는 방법이다. 모든 문제들에 적용 가능한 방식은 아니지만 동적 프로그래밍을 적용할 수 있는 문제의 경우엔 코드의 성능에서 큰 이점을 가진다.

DP 요구조건 1 - 중첩되는 하위 문제가 있어야 한다.

  • 대표 유형: 피보나치 수열 문제

문제 내에서 어떤 방식으로든 반복되는 하위 문제들이 있어야 한다. 즉, 하나의 커다란 문제를 더 작은 문제들로 나눌수 있으며 그 나뉜 작은 문제들을 재사용 가능해야 한다.

피보나치 수열은 모든 숫자가 그 앞에 오는 두 개의 숫자의 합과 같은 숫자로 이루어진 수열을 말한다. 이를 트리 구조로 살펴보면 어떠한 숫자를 구하기 위해 이전의 두 숫자와 그 더한 값을 알아야 하며 이 부분이 중복되어 등장하기 때문에 한 문제를 여러 문제들로 나누며 이 하위 문제들이 반드시 반복되어야 하는 동적 프로그래밍의 요구 조건에 부합한다.

overlapping subproblems : 중첩되는 하위 문제들

적용이 불가능한 경우

  • 대표 유형: 병합 정렬(merge sort) - 배열을 더 작은 조각으로 나누어서 정렬하고 다시 합침.

하나의 문제를 작은 문제들로 나누는 건 동일하나, 매번 다른 조각들을 정렬하기 때문에 하위 문제들이 중복되어 재사용할 수 있는 경우가 아니므로 (No overlapping subproblems.) DP 적용 불가능. 이런 유형은 분할 정복으로 풀면 된다.

DP 요구조건 2 - 최적의 부분 구조(optimal substructure)

하위 문제들의 최적 해답으로 문제의 최적 해답을 구할 수 있는 경우를 문제가 optimul substructure를 가진다고 말한다.

  • 최단 경로 문제
    1. A → B 의 최단 경로를 찾는다. (하위 문제)
    2. A → C 의 최단 경로를 찾는다. (하위 문제)
    3. A → D 의 최단 경로를 찾는다.

그래프의 최단 경로에서 현재 A부터 D까지 가는 최단 경로의 문제 안에 A부터 C까지 가는 최단 경로의 작은 문제가 중첩되어 있다. 이러한 형태를 optimal substructure를 가졌다고 한다. 더 쪼개면 A부터 B까지 가는 최단 경로의 작은 문제도 중첩되어 가지고 있음을 알 수 있다.

시작 지점에서 끝 지점으로 가는 최단 경로에 관한 문제에서는 최단 경로 위에 있는 한 지점에 대해서 시작부터 해당 지점까지가 항상 최단 거리가 된다. 따라서 하위 문제가 중첩된다.

최적 경로가 아닌 경우

중간 기착지를 거쳐야 하는 항공편이 있을 때 최단 경로가 아니라 ‘최저가’인 경로를 찾는 경우 같은 때에는 최단 경로와 가장 저렴한 경로가 항상 일치하지는 않는다. 즉 중첩되는 하위 문제가 없고 최적 경로가 없다.

2. DP의 재귀방식 솔루션 - 피보나치 수열 예시

메모이제이션 미적용 피보나치 수열 의사 코드 - O(2^n)

// fib(n) = fib(n - 1) + fib(n - 2)
// base case가 fib(2) = 1,  fib(1) = 1 로 주어진다고 가정.

function fib(n) {
// 현재 주어진 1번째, 2번째 숫자가 1이라서 주어진 재귀함수의 탈출조건이 아래와 같다.
	if (n <= 2) return 1;
	return fib(n - 1) + fib(n - 2); // 반환문에서 재귀 호출.
}

상기 이미지와 같이 피보나치 수열의 수가 늘어날 수록 트리 가짓수가 굉장히 늘어나므로 dp 없이 재귀만으로는 상당히 비효율적이다. 시간복잡도는 대략 O(2^n) 이다.

Memoization: 하향식 접근

메모이제이션이란 일반적으로 배열이나 객체로 데이터를 저장할 구조를 만든 다음 시간이 오래 걸리는 함수를 실행시키고(expensive function calls) 그 값을 배열에 저장해두고 재사용하는 방법을 말한다(returning the cached result when the same inputs occur again). 즉 상위 문제에서 하위 문제가 중첩될 때 이미 푼 하위 문제를 다시 풀지 않고 걸과를 재사용하는 것이다.

메모이제이션 적용 피보나치 수열 재귀 코드 - O(N)

// * 1. Memoization 방식 피보나치 수열 재귀 코드 - O(N)
// 피보나치 수열의 첫번째, 두번째 숫자가 1인 경우를 가정한다.
// 메모이제이션 할 배열의 인덱스 0에는 값을 담지 않는 예시이다.
// 찾고자 하는 피보나치 숫자가 몇번째인지와 배열에 담아둔 인덱스를 일치시키기 위함.
// 메모이제이션 할 memo 배열에 아래 예제 코드처럼
// 1. 바로 값을 넣어두어도 되고
// 2. 빈 배열로 두어도 되며
// 3. 아예 독립된 변수로 만들어도 된다.

function fib(n, memo = [undefined, 1, 1]) {
  if (memo[n] !== undefined) return memo[n];
  //   if (n <= 2) return 1; -> base case의 필요가 없어짐. memo 배열에 초기값이 있으므로!
  let result = fib(n - 1, memo) + fib(n - 2, memo);
  memo[n] = result;
  return result;
}

Tabulation: 상향식 접근

반복문을 사용하며, 메모이제이션과 반대로 가장 작은 하위 문제를 푼 결과를 테이블(주로 배열)에 저장하고 반복문을 실행하면서 상향식으로 문제를 접근한다.

// * 2. Tabulation 방식 피보나치 수열 코드 - 재귀 x, 반복문 사용.
// 반복문을 사용하며, 메모이제이션과 반대로 가장 작은 하위 문제를 푼 결과를
// 테이블(주로 배열)에 저장하고 반복문을 실행하면서 상향식으로 문제를 접근한다.
// 피보나치 수열의 첫번째, 두번째 숫자가 1로 주어짐을 가정하고 배열에 [x, 1, 1] 로 초기화.
// n번째 피보나치 수열의 숫자를 구한다.
// fib(1), fib(2) 가 이미 1로 주어져 있으므로 i는 3부터 시작.
// 따라서 fib(3)부터 fib(4), fib(5) 즉 상향식으로
// 값을 배열에 fibNumbs[i] = fibNumbs[i - 1] + fibNumbs[i - 2]로 저장해 나간다.
function fib(n) {
  if (n <= 2) return 1;
  let fibNumbs = [undefined, 1, 1]; // 인덱스 0번은 사용하지 않는 예제 코드.
  for (let i = 3; i <= n; i++) {
    fibNumbs[i] = fibNumbs[i - 1] + fibNumbs[i - 2];
  }
  return fibNumbs[n];
}

 

728x90
저작자표시 비영리 변경금지 (새창열림)

'개발 > 알고리즘' 카테고리의 다른 글

자바스크립트로 설명하는 재귀함수  (0) 2022.06.01
  • 1. 정의
  • DP 요구조건 1 - 중첩되는 하위 문제가 있어야 한다.
  • 적용이 불가능한 경우
  • DP 요구조건 2 - 최적의 부분 구조(optimal substructure)
  • 최적 경로가 아닌 경우
  • 2. DP의 재귀방식 솔루션 - 피보나치 수열 예시
  • 메모이제이션 미적용 피보나치 수열 의사 코드 - O(2^n)
  • Memoization: 하향식 접근
  • 메모이제이션 적용 피보나치 수열 재귀 코드 - O(N)
  • Tabulation: 상향식 접근
'개발/알고리즘' 카테고리의 다른 글
  • 자바스크립트로 설명하는 재귀함수
FE RYAN
FE RYAN

티스토리툴바

개인정보

  • 티스토리 홈
  • 포럼
  • 로그인

단축키

내 블로그

내 블로그 - 관리자 홈 전환
Q
Q
새 글 쓰기
W
W

블로그 게시글

글 수정 (권한 있는 경우)
E
E
댓글 영역으로 이동
C
C

모든 영역

이 페이지의 URL 복사
S
S
맨 위로 이동
T
T
티스토리 홈 이동
H
H
단축키 안내
Shift + /
⇧ + /

* 단축키는 한글/영문 대소문자로 이용 가능하며, 티스토리 기본 도메인에서만 동작합니다.