📌 캐싱을 사용하는 최적화 기법
- 최적화 문제를 해결
- 중복된 연산을 피하기 위해 사용
- 작은 하위 문제들의 해결책을 조합 ⇒ 전체 문제를 해결
📌 주로 사용되는 예제
- 피보나치 수열
- Longest Common Substring (최장 공통 부분 문자열)
- 0/1 Knapsack Problem (0/1 배낭 문제)
- 그래프의 최단 경로
📌 추가 문제
1. House Robber
2. Best Time to Buy and Sell Stock
3. Climbing StairsCodeSandbox : 코드 샌드박스 예제 페이지
👉 문제를 작은 하위 문제로 쪼갤 수 있고, 반복적인 재귀 해결책들의 해결책 또는 결과를 캐싱하여, 성능을 개선할 수 있을 때 사용합니다.
algorithms\dynamic-programming\memoizationDP.js
let cache = {
// property에 접근 : O of One Time
1: 81,
};
function memoizedAddTo80(n) {
if (n in cache) {
return cache[n];
} else {
console.log("처리 중...");
cache[n] = n + 80;
return cache[n];
}
}
console.log("첫 번째: ", memoizedAddTo80(1)); // 81, 연산 후 캐싱
console.log("두 번째: ", memoizedAddTo80(1)); // 81, O of One
algorithms\dynamic-programming\memoizationDPWithCloser.js
function memoizedAddTo80WithCloser(n) {
let cache = {};
return function (n) {
if (n in cache) {
return cache[n];
} else {
console.log("처리 중...");
cache[n] = n + 80;
return cache[n];
}
};
}
const memoized = memoizedAddTo80WithCloser(); // cache 변수와 내부 함수 반환
console.log("첫 번째: ", memoized(1)); // 81, 연산 후 캐싱
console.log("두 번째: ", memoized(5)); // 85, 연산 후 캐싱
console.log("세 번째: ", memoized(5)); // 85, O of One
클로저를 이용하여 cache 객체를 선언하고, 해당 함수 내부에서만 접근할 수 있도록 변수 환경에 저장합니다. 이후 함수를 호출할 때마다 해당 변수 환경에서 cache 객체를 활용하여 이미 계산된 값을 반환하게 됩니다. 이렇게 함으로써, 전역 변수를 사용하지 않고도 값을 캐싱하고 재사용함으로써 성능을 향상시킬 수 있습니다.
✏️ 실행 컨텍스트
코드 실행에 필요한 변수, 함수 선언, 매개변수 등의 정보를 담고 있는 스택(Stack) 자료구조 형태(LIFO)
- 구분: Variable Environment, Lexical Environment, This Binding
- 생성: 코드를 실행할 때
- 제거: 함수 실행이 종료될 때
클로저 함수를 다음과 같이 사용할 수 없는 이유를 묻는다면...
console.log("첫 번째: ", memoizedAddTo80WithCloser()(1));
console.log("두 번째: ", memoizedAddTo80WithCloser()(1));
✏️
memoizedAddTo80WithCloser()
함수는 내부에서 캐싱을 위한cache
변수와 내부 함수를 반환합니다.매번
memoizedAddTo80WithCloser()
함수가 호출될 때마다cache
객체가 초기화됩니다. 그렇기 때문에cache
객체에 저장된 값은 재사용되지 않고 매번 새로 계산됩니다.즉,
memoizedAddTo80WithCloser()(1)
과memoizedAddTo80WithCloser()(1)
의 결과는 서로 다를 수 있습니다.변수를 함수가 최초 호출될 때 cache 객체가 생성되고, 이후 호출에서 계산 결과를 재사용하는 캐싱을 사용할 수 있습니다.
let cacluations = 0;
function fibonacci(n) {
cacluations++;
if (n < 2) {
return n;
}
return fibonacci(n - 1) + fibonacci(n - 2);
}
const fibonacciIndex = 9;
const fibonacciNumber = fibonacci(fibonacciIndex);
console.log(`
fibonacciIndex: ${fibonacciIndex},
fibonacciNumber: ${fibonacciNumber},
cacluations: ${cacluations}
`);
📎 Top-down 방식은 일반적으로 재귀적인 방식으로 문제를 해결하는 방식에서 시작합니다. 하지만 이때, 중복되는 계산을 피하기 위해 memoization(메모이제이션)을 적용합니다.
즉, 문제를 재귀적으로 해결하면서 이미 계산한 결과를 메모해 놓고, 같은 계산이 다시 나타나면 메모한 결과를 리턴하는 방식입니다. 이를 통해 중복 계산을 피할 수 있어서 계산 속도를 대폭 개선할 수 있습니다.
algorithms\dynamic-programming\fibonacciDP.js
let cacluations = 0;
function fibonacciFn() {
let cache = {}; // 해시 테이블에 피보나치 계산값을 캐싱합니다.
return function fib(n) {
cacluations++;
if (n in cache) {
return cache[n];
} else {
if (n < 2) {
return n;
} else {
cache[n] = fib(n - 1) + fib(n - 2);
return cache[n];
}
}
};
}
const fibDP = fibonacciFn(); // 변수에 함수를 할당
const fibonacciIndex = 20;
const fibonacciNumber = fibDP(fibonacciIndex);
// if fibonacciIndex: 9, cacluations: 109
// O of Two to the Power of N
console.log(`
Caculate Fibonacci with DP
-----------------------------
fibonacciIndex: ${fibonacciIndex},
fibonacciNumber: ${fibonacciNumber},
cacluations: ${cacluations}
`);
📎 Bottom-up 방식은 동적 프로그래밍은 주어진 문제의 해결을 위해, 작은 부분 문제들의 해결을 먼저 구하고, 이들의 해결 결과를 이용하여 보다 큰 문제의 해결을 차례대로 구해나가는 방법입니다.
Bottom-up 동적 프로그래밍은 주어진 문제의 해결을 위해, 작은 부분 문제들의 해결을 먼저 구하고, 이들의 해결 결과를 이용하여 보다 큰 문제의 해결을 차례대로 구해나가는 방법입니다.
피보나치 예시
// fibonacciBottomUp 함수 정의, n을 인자로 받음
function fibonacciBottomUp(n) {
// 초기값 설정
let answer = [0, 1];
for (let i = 2; i <= n; i++) {
answer.push(answer[i - 2] + answer[i - 1]);
// i-2번째와 i-1번째 항을 더한 값을 answer 배열에 추가
}
// 구한 값들 중 n번째 값 반환
return answer.pop();
}
📎 함수 내부에서 answer 배열을 선언하고, 초기값으로 [0, 1]을 할당합니다. 이후 for 문을 통해 배열에 추가할 값을 계산하며, 마지막에 answer.pop()을 통해 n번째 항을 반환합니다.
Bottom-up 방식에서는 계산해야 하는 값을 작은 단위부터 계산해나가기 때문에 재귀 호출에 비해 더 빠른 속도로 수열을 구할 수 있습니다.
- 재귀를 사용하여 반복적인 연산을 하는 함수에 동적 프로그래밍 기법을 사용할 수 있다.
- closer를 의식적으로 활용해보자.
- 동적 프로그래밍에 대한 예제를 풀어보자.
- 피보나치 수열
- Longest Common Substring (최장 공통 부분 문자열)
- 0/1 Knapsack Problem (0/1 배낭 문제)
- 그래프의 최단 경로
- House Robber
- Best Time to Buy and Sell Stock
- Climbing Stairs
- 콘솔 내용처럼 calcuations 횟수를 개선할 수 있다.