# 다이나믹 프로그래밍 : 큰 문제를 작은 나눠서 푸는 방식의 프로그래밍
# 큰 문제를 작은 나눠서 푸는 방법 2가지 : 다이나믹 프로그래밍(Dynamic Programming), 분할 정복 알고리즘(Divide & Conquer)
# 다이나믹 프로그래밍으로 풀 수 있는 문제들이 갖는 2가지 속성 : Overlapping Subproblem, Optimal Substructure
1. Overlapping Subproblem(겹치는 부분 문제)
- 큰 문제의 부분 문제들의 중복이 존재해야 한다.
- 문제들이 겹치므로 큰 문제는 부분 문제들을 푸는 방법과 같은 방법으로 풀 수 있다. -> 주로 재귀를 사용
2. Optimal Substructure(최적 부분 구조)
- 큰 문제의 정답을 부분 문제의 정답을 통해 구할 수 있다.
- 문제의 크기에 상관 없이 어떤 한 문제의 정답은 일정해야 한다.
ex) 피보나치 수를 구하는 방법 : F(n) = F(n-1) + F(n-2)
큰 문제 : F(n), 부분 문제 : F(n-1), F(n-2)
1. Overlapping Subproblem : F(n)을 구하기 위해 필요한 부분 문제 F(n-2)는 F(n-1)을 구하는데 필요한 부분 문제이기도 하다.(중복됨)
2. Optimal Substructure : 부분 문제인 F(1)과 F(0)을 풀면 큰 문제인 F(2)를 풀 수 있고, 부분 문제인 F(n-1)과 F(n-2)를 풀면 큰 문제인 F(n)을 풀 수 있다.
# 다이나믹 프로그래밍의 방법 : 부분 문제들을 해결해서 얻은 답을 기록해둔다.(다시 연산할 필요가 없도록) -> Memorization
피보나치를 예로 들면 F(n)을 구하기 위해 F(0), F(1), ..., F(n-2), F(n-1)의 답들을 기록해둔다.
5번째 피보나치 수를 구하면서 풀게되는 부분 문제들이다.
그림을 보면 중복되는 부분 문제들이 존재하는 것을 확인할 수 있다.
중복되는 부분 문제들을 맞닥뜨릴 때마다 연산하지 않고, 전에 풀고 기록해둔 답을 가져와서 쓴다면 큰 문제를 해결하는데 소요되는 시간을 줄일 수 있다.
public class Doodle {
int[] memo = new int[100]; // memorization을 위한 기록 배열
int fibonacci_with(int n) { // memorization 사용
if (n <= 1) {
return n;
} else {
if (memo[n] > 0) { // 전에 푼적이 있는 작은 문제라면
return memo[n]; // 또 풀지 말고 기록해두었던 답을 가져와서 사용
}
memo[n] = fibonacci_with(n-1) + fibonacci_with(n-2);
return memo[n];
}
}
int fibonacci_without(int n) { // memorization 사용하지 않음
if (n <= 1) {
return n;
} else {
return fibonacci_without(n-1) + fibonacci_without(n-2);
}
}
}
위 함수는 memorization을 사용한 것, 아래 함수는 사용하지 않은 것임.
memorization을 사용하지 않은 알고리즘의 경우, 큰 문제를 1개 해결하기 위해 부분 문제 2개를 풀어야 하고 각 부분 문제를 풀기 위해서도 2개의 부분 문제를 풀어야 한다. 결국 n번째 피보나치 수를 구하기 위해서는 2*2*2*... = 2^n 만큼 문제를 푸는 연산을 해야 한다.
문제 1개를 푸는데 걸리는 시간이 O(1)이므로
총 O(2^n)의 시간복잡도를 갖는다.
반면, memorization을 사용한 알고리즘의 경우, n번째 피보나치 수를 구하기 위해 문제 해결 연산을 n번만 수행하면 된다. 부분 문제들이 중복된다 하더라고 1번 연산을 수행했으면 그 답을 기록한 것을 사용하면 되기 때문이다.
결국 총 O(n)의 시간복잡도를 갖는다.
memorization을 사용하지 않은 알고리즘에 비해 월등히 빠른 것을 확인할 수 있다.
# 다이나믹 프로그래밍의 구현 방식
1. Top-Down : 큰 문제를 조금씩 작게 만들어가는 것
- 큰 문제부터 문제를 쪼개가면서 부분 문제들로 만드는 것을 반복하여, 부분 문제를 푸는 것으로 큰 문제를 해결
2. Bottom-Up : 작은 문제를 조금씩 크게 만들어가는 것
- 부분 문제들을 모아서 큰 문제를 하나 만들어가는 것을 반복하며 큰 문제를 해결
ex) 위의 피보나치 수를 구하는 알고리즘은 Bottom-Up 방식의 예이다.
두 방법에 시간 차이가 존재하는가? -> 단정지을 수 없다.
재귀를 사용하는 Top-Down이 더 오래걸릴것이라고 느낄 수 있다.
(재귀마다 변수들을 생성하고, 스택오버플로우 문제 발생 가능)
그러나 Bottom-Up 방식은 무조건 존재하는 모든 부분 문제들을 풀어야 큰 문제를 풀 수 있다는 단점을 가진다.
결국 어떤 방식이 시간이 더 적게 소요된다고 단정지을 수 없다.
다만 파이썬은 Bottom-Up을 추천하고
스택오버플로우 문제가 상대적으로 잘 발생하지 않는 C나 JAVA는 Top-Down을 추천한다.
# 다이나믹 프로그래밍 문제 풀이는 점화식을 정의하는 것으로부터 시작한다.
'알고리즘' 카테고리의 다른 글
알고리즘) 트리(Tree) (0) | 2020.08.17 |
---|---|
알고리즘) 그래프의 탐색 - DFS, BFS (0) | 2020.08.17 |
알고리즘) 그래프(Graph) (0) | 2020.08.17 |
알고리즘)브루트 포스(Brute Force)와 순열(permutation) (0) | 2020.08.05 |
알고리즘) 수학 - 나머지 연산, 최대공약수(GCD), 최소공배수(LCM), 소수, 팩토리얼 (0) | 2020.07.18 |
전체 글
133개알고리즘) 다이나믹 프로그래밍(Dynamic Programming)이 무엇인가?
# 다이나믹 프로그래밍 : 큰 문제를 작은 나눠서 푸는 방식의 프로그래밍 # 큰 문제를 작은 나눠서 푸는 방법 2가지 : 다이나믹 프로그래밍(Dynamic Programming), 분할 정복 알고리즘(Divide & Conquer) # 다이나믹 프로그래밍으로 풀 수 있는 문제들이 갖는 2가지 속성 : Overlapping Subproblem, Optimal Substructure 1. Overlapping Subproblem(겹치는 부분 문제) - 큰 문제의 부분 문제들의 중복이 존재해야 한다. - 문제들이 겹치므로 큰 문제는 부분 문제들을 푸는 방법과 같은 방법으로 풀 수 있다. -> 주로 재귀를 사용 2. Optimal Substructure(최적 부분 구조) - 큰 문제의 정답을 부분 문제의 정답을..
알고리즘 2020.07.20 starmk95JAVA)1935번 후위 표기식 2 - Stack(스택) 사용
해설은 주석 참고 import java.util.Scanner; import java.util.Stack; public class Main { public static void main(String[] args) { Stack operand = new Stack(); // 피연산자 스택 Scanner sc = new Scanner(System.in); int n = sc.nextInt(); // 피연산자의 개수 Double[] operandArray = new Double[n]; // 피연산자들을 저장하는 배열 String cmd = sc.next(); // 수식 받아오기 for (int i=0;i ASCII 코드 사용 // 각 알파벳은 A = 65, ..., Z = 90의 아스키 코드 값을 갖는다. /..
알고리즘/백준 알고리즘(JAVA) 2020.07.19 starmk95JAVA)1373번 2진수 8진수로 변환하기
2진수의 각 3자리는 8진수의 1자리로 변환된다. import java.util.Scanner; public class Main { public static void main(String[] args) { Scanner sc = new Scanner(System.in); String binaryNum = sc.nextLine(); // 2진수를 8진수로 변환하면 2진수의 자리수 3개가 8진수 자리수 1개로 변환된다. if (binaryNum.length()%3 == 1) { // 2진수 가장 앞의 한자리가 8진수의 첫자리를 의미 System.out.print(Character.getNumericValue(binaryNum.charAt(0))*1); } else if (binaryNum.length()%3..
알고리즘/백준 알고리즘(JAVA) 2020.07.19 starmk95JAVA)17087번 숨바꼭질 6
수빈이가 이동하는 방법은 +D 또는 -D 밖에 없다. -> 수빈이 동생을 찾기 위해서는 수빈과 동생과의 거리가 D의 배수여야 한다. 동생이 한명이라면? -> D = 수빈과 동생과의 거리의 약수이면 된다. 동생이 n명이라면? -> D = 수빈과 동생들과의 거리들의 공약수이면 된다. D의 최대값 = 거리들의 최대공약수 즉, 최대공약수를 구하는 알고리즘을 사용하여 해결하면 된다. 최대공약수를 구하는 알고리즘은 유클리드 호제법을 사용했다. import java.util.Scanner; public class Main { public static void main(String[] args){ Scanner sc = new Scanner(System.in);; int n = sc.nextInt(); // 동생 n명..
알고리즘/백준 알고리즘(JAVA) 2020.07.19 starmk95JAVA)1676번 팩토리얼에서 0의 개수 구하기
팩토리얼에서 0의 개수란? ex) 10! = 362800 -> 2개 팩토리얼에서 0의 개수를 구하는 방법은 팩토리얼의 인수에 2*5(10)이 들어있는 개수를 구하면 된다. (0을 만들 수 있는 방법이 2*5밖에 없기 떄문) 인수들에서 5를 포함하는 수보다(5의 배수) 2를 포함하는 수가 더 많기 떄문에 5를 포함하는 인수들의 개수를 구해주면 팩토리얼에 들어가는 0의 개수를 알아낼 수 있다. 이 때, 5*5 또는 5*5*5처럼 5가 여러번 들어가는 경우도 고려해주어야한다. ex) 입력이 100이라고 가정하면, 100/5 = 20 -> 5가 20번 들어감 100/25 = 4 -> 5가 2개 들어가는 경우는 4번 따라서 20 + 4 = 24 100!에 들어있는 0의 개수는 총 24개이다. import java..
알고리즘/백준 알고리즘(JAVA) 2020.07.19 starmk95JAVA)1929번 M이상 N이하의 소수구하기 - 에라토스테네스의 체
소수의 개수를 파악하는데 에라토스체네스의 체를 사용했다. 에라토스체네스의 체는 2개의 단계를 반복 수행하는 것으로 이루어진다. 1. 지워지지 않은 수 중에서 가장 작은 수 -> 소수이다. 2. 1단계에서 구한 소수의 배수들을 모두 지원준다. // 해당과정 반복 설명은 주석 참고 import java.util.Scanner; public class Main { public static void main(String[] args) { Scanner sc = new Scanner(System.in); int sNum = sc.nextInt(); int eNum = sc.nextInt(); int[] prime = new int[eNum-sNum+1]; // 소수 저장 int pn = 0; // 소수의 개수 b..
알고리즘/백준 알고리즘(JAVA) 2020.07.19 starmk95JAVA)1978번 소수 판별 문제
소수를 판별하기 위해서는 그 수가 소인수(자연수를 나누어 떨어뜨리는 약수 중 소수인 수)를 갖는 지 확인해야한다. 어떤 수가 소수가 아니라면, 그 수(N)는 N = a*b (a, b는 소수)꼴로 나타낼 수 있다. 이 때, a>b 라고 하면, a는 언제나 루트(N)보다 작은 값을 가지고, b는 루트(N)보다 큰 값을 가진다. 그러므로 루트(N)보다 작은 수에 대해서만 N이 나누어떨어지는지 확인하면 N이 소수인지의 여부를 파악할 수 있다. 반복문은 루트(N)번 반복되며 시간 복잡도는 O(루트(N))이다. import java.util.Scanner; public class Main { public static void main(String[] args) { Scanner sc = new Scanner(Sys..
알고리즘/백준 알고리즘(JAVA) 2020.07.19 starmk95JAVA) 2609번 최대공약수와 최소공배수 구하기
최대공약수를 구하기 위해 유클리드 호제법을 사용했고 최소공배수를 사용하기 위해 구한 최소공배수 값을 활용했다. cf) 유클리드 호제법 : GCD(a, b) = GCD(b, r)이다. 이 때, r = a를 b로 나눈 나머지이다. r = 0이 될때, b가 최대공약수가 된다. ex) GCD(24, 16) = GCD(16, 8) = GCD(8, 0) -> 최대공약수 = 8 cf) 최소공배수 = gcd*(a/gcd)*(b/gcd)이다. import java.util.Scanner; public class Main { public static void main(String[] args) { Scanner sc = new Scanner(System.in); int num1 = sc.nextInt(); int num2..
알고리즘/백준 알고리즘(JAVA) 2020.07.18 starmk95알고리즘) 수학 - 나머지 연산, 최대공약수(GCD), 최소공배수(LCM), 소수, 팩토리얼
1. 나머지 연산 - 덧셈 : (A+B)modM = ((AmodM)+(BmodM))modM - 곱셍 : (A*B)modM = ((AmodM)*(BmodM))modM - 뺄셈 : (A-B)modM = ((AmodM)-(BmodM)+M)modM cf) 왜 덧셈, 곱셈과 달리 +M을 해주는가? ex) ((6%3)-(5%3))%3 = -2%3 = ? 이 답은 -2인가 1인가 -> 언어마다 답이 다르다. (Java, C는 -2, Python은 1이라고 답함) 이를 방지하기 위해 값이 음수로 나와도 이를 양수로 바꾸고 연산하기 위해 +M을 해주는 것임. 2. 최대공약수 GCD(Greatest Common Divisor), 최소공배수(Least Common Multiple) int gcd = 1; for (int i..
알고리즘 2020.07.18 starmk95JAVA)17299번 오등큰수 구하기 - Stack(스택) 사용
import java.io.*; import java.util.Stack; public class Main { public static void main(String[] args) throws IOException { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out)); int len = Integer.parseInt(br.readLine()); String cmd = br.readLine(); Stack stack = new Stack(); String[] stringArray = cmd.s..
알고리즘/백준 알고리즘(JAVA) 2020.07.18 starmk95JAVA) 변수들의 자료형에 따른 초기값
- byte : 0 - short : 0 - int : 0 - long : 0 - char : 0 - float : 0.0 - double : 0.0F - boolean : false - String, 배열 등 : null
JAVA 2020.07.17 starmk95JAVA)17298번 오큰수 구하기 - Stack(스택) 사용
지금까지 푼 문제중에서는 제일 헷갈렸다.. 해설은 코드 내 주석 참고 import java.io.*; import java.util.Stack; public class Main { public static void main(String[] args) throws IOException { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out)); int len = Integer.parseInt(br.readLine()); String cmd = br.readLine(); Stack stack = ne..
알고리즘/백준 알고리즘(JAVA) 2020.07.17 starmk95