Recursion
- Simplify and Delegate
- Reduction
- 종료 조건: Base case
Correctness: Induction으로 증명
Time complexity analysis: 점화식부터 구한다
Divide-and-Conquer
나누고(Divide) 정복한다(Conquer) -> 분할정복
Recursion 활용
Ex) Mergesort, Quicksort
Pattern
분할정복은 일반적으로 다음의 세 단계로 진행된다.
① Divide
문제를 나눈다.(Mergesort에서는 2개)
같은 문제의 더 작은 instance로 나눈다.
② Delegate (Conquer)
각각의 더 작은 instance에 대해 재귀호출을 한다.
③ Combine
Mergesort의 Merge에 해당
계속 문제를 나누다 보면 base case에 도달하는데, 이 때는 직접 문제를 풀면 된다.
정렬의 경우 정렬해야 되는 수의 개수가 1개면 할 게 없는데, 이 때가 base case.
-> Combine 단계에서 어떻게 할 지를 고민하면 됨
mergesort의 경우 어떻게 합쳐야 정렬이 될 지를 고민하면 됨
대부분 Induction으로 증명
Time complexity: 재귀 알고리즘이므로 점화식(recurrence)을 찾는게 우선
Ex) Sum
input: n
Output: 1+2+3+⋯+n
Iteration: O(n)
s←0
fori←1ton
s←s+i
returns
Recursion: O(n)
ifn=1return1
returnn+sum2(n−1)
* Recurrence
T(n)={1(n=0)1+T(n−1)(n>1)→T(n)=n=O(n)
Divide and Conquer
i) n이 짝수라면
1+2+⋯+n2+(n2+1)+⋯+n
cf. 주어진 문제는 1부터 n까지의 합이므로 n2 뒷부분인 (n2+1)+⋯+n은 '같은 문제에 대한 instance'가 아니다. 즉 이 부분은 재귀호출로 해결할 수 없다.
앞부분은 됨
1+2+⋯+n2+(n2+1)+⋯+n
=1+⋯+n2+(n2+1)+(n2+2)+⋯+(n2+n2)
여기서 뒷부분에서 n2를 빼면 1+2+⋯+n2과 같다는 것을 알 수 있다.
따라서
1+2+⋯+n2+(n2+1)+⋯+n
=1+⋯+n2+(n2+1)+(n2+2)+⋯+(n2+n2)
=(1+⋯+n2)+(n2)2+(1+⋯+n2)
-> sum(n2)의 값을 구해 2를 곱한 후 (n2)2를 더하면 됨
sum(n2)은 원래 문제의 더 작은(절반짜리) instance
n이 홀수라면
(1+⋯+n−12)+(n−12+1)+⋯+n
(1+⋯+n−12+(n−12+1)+⋯+((n−12)+(n−12)+n
=2(1+⋯+n−12+(n−12)2+n
따라서 Pseudocode는
sum(n)
ifn=1return1
ifniseventhen
return2∗sum3(n/2)+(n/2)2
else
return2∗sum((n−1)/2)+((n−1)/2)2+n
* Recurrence
T(n)={O(1)(n=1)1+T(n2)+O(1)(n>1)→T(n)=n=O(n)
→T(n)=O(log2n)
: 앞의 O(n)에 비해 매우 빠름(더 빠른 알고리즘도 있다)
시간복잡도에서 ⌊⌋같은 건 무시해도 됨. 원래 점화식은 T(⌊n2⌋)+O(1)
Ex) Max
Input: array A[0..n−1]
Output: maximum element in A
- Divide and Conquer
A를 반으로 나눠 (A1,A2) 각각의 최댓값 m1과 m2를 구하면, 둘 중에 더 큰 값이 배열 전체의 최대값이 된다.
max(A[0..n−1]
ifn=1returnA[0]
mid←n/2
m1←max(A[0..mid])
m2←max(A[mid+1..n−1])
ifm1>m2
thenreturnm1
elsereturnm2
T(n)={O(1)(n=1)2T(n2)+O(1)
∴
비교를 (n-1)보다 적게 하고 최댓값을 찾을 수 없다 -> O(n)보다 빠른 알고리즘은 만들 수 없다.
Recursion tree
재귀호출이 이루어지는 과정을 도식화한 것
보통 Tree 형태를 띤다.
처음 호출이 root node가, 재귀호출한 게 자식 노드가 됨
맨 밑의 leaf node가 Base case
각 노드의 값은 non-recursive part의 시간복잡도이며 각 노드는 재귀호출만큼의 자식 노드가 생긴다.
따라서 각 노드의 값을 모두 더하면 전체 알고리즘의 running time이 됨
Ex) Max
max(A[0..n-1])
if\;\; n=1\; return\; A[0]
mid\; \leftarrow\; n/2
m1\; \leftarrow\; max(A[0..mid])
m2\; \leftarrow\; max(A[mid+1..n-1])
if\;\; m1>m2
\;\;\;\;then\;\;return\;\;m1
else\;\;return\;\;m2

이 경우 모든 호출에서 연산 횟수가 상수번이므로 시간복잡도는 C \times (the\;\;number\;\; of\;\; nodes)가 된다.
Leaf node의 수는 n개고, Leaf node의 수는 n개인 node의 이진트리의 전체 수는 n-1 + n = 2n-1개다.
따라서 시간복잡도는 C \times (2n - 1) = O(n)이 된다.
Ex) Mergesort

재귀호출 부분을 제외하면 Merge 함수 호출 + 1회의 연산이 이루어지며, Merge 함수는 O(n)의 시간복잡도를 가진다.
또한 Mergesort의 점화식은 T(n) = 2T(\frac{n}{2}) + O(n)이므로 Root node의 value는 cn이 된다.

각 계층 별로 노드의 value를 모두 더하면 cn이 됨을 알 수 있다. 그리고 포화 이진 트리의 높이는 \log_2{n}이므로
시간복잡도는 cn\log_2{n} = O(n\log{n})이 된다. (원래는 cn\lceil \log{n} \rceil)
Recursion tree for general Divide-and-Conquer Algorithms
r번의 재귀호출 subProblem의 크기가 n/c, 재귀호출을 뺀 연산 횟수가 O(f(n))이면 점화식은
T(n) = rT(n/c) + O(f(n))
이 된다.
이 경우 Recursion Tree는 다음과 같다.

따라서 점화식은 T(n) = \sum_{i=0}^L r^i \cdot f(n/c^i)
L = tree\;\;depth = \log_c{n}
: 재귀 호출은 \frac{n}{c^L} = 1이 될 때까지, 즉 \log_c{n}=L이 될 때까지 반복한다.
#leaves = r^L = r^{\log_c{n}} = n^{\log_c{r}}
f(n)에 대해서는 다음의 세 가지 경우가 있다.
ⓐ f(n)이 n^{\log_c{r}}보다 더 빠르게 증가하는 함수일 경우 (f(n) = \Omega(n^{\log_c{r + \epsilon}}))
\;\;then\;\; T(n) = O(f(n))
ⓑ f(n) = \Theta(n^{\log_c{r}}\log^k{n})\;\;for\;\;k \geq 0로 표현할 수 있다면
T(n)=O(f(n)\cdot\log{n})
ⓒ f(n)이 n^{\log_c{r}}보다 더 느리게 증가하는 함수일 경우(f(n) = O(n^{\log_c{r - \epsilon}}))
T(n) = O(n^{\log_c{r}})
Master Theorem
ⓐ If\;\;f(n) = \Omega(n^{\log_c{r} + \epsilon}) -> f(n)의 지수가 \log_c{r}에 비해 크면
T(n) = O(f(n))
ⓑ If\;\;f(n) = \Theta(n^{\log_c{r}}\log^k{n}) \;\;for\;\;some\;\; k \geq 0 -> f(n)의 지수가 \log_c{r}과 비슷하면
T(n)=O(f(n)\cdot\log{n})
ⓒ If\;\;f(n) = O(n^{\log_c{r} - \epsilon}) -> f(n)이 n^{\log_c{r}}에 비해 작으면
T(n) = O(n^{\log_c{r}})
Ex) T(n) = 2T(\frac{n}{2}) + O(n)
r=2, c=2, f(n)=n -> \log_c{r} = 1
ⓐ f(n) = \Omega(n^{(1 + \epsilon)})
ⓑ f(n) = \Theta(nlog^k{n})
ⓒ f(n) = O(n^{(1 - \epsilon)})
ⓐ: f(n) = n^1이므로 안 됨(n^1이 n^{1.000001}보다 빠르게 증가하는 함수가 아니니까)
ⓑ k=0인 경우 됨 -> T(n) = O(f(n) \cdot \log{n}) = O(n\log{n})
ⓒ: ⓐ와 같은 논리로 안 됨 (n^1이 n^{0.999}보다 느리게 증가하는 함수가 아니니까)
Ex) T(n) = T(\frac{n}{2}) + O(n)
c = 2, r = 1, f(n)=n, \log_c{r} = 0
n^{\log_c{r}} = n^0은 f(n) = n^1보다 느리게 증가함 -> ⓒ에 해당함
: n^r과 f(n)에서의 지수를 비교했을 때 차이가 분명하다면 그를 토대로 ⓐ인지 ⓒ인지 판단할 수 있다.
\therefore\;\; T(n) = O(f(n)) = O(n)
Ex) T(n) = T(\frac{n}{2}) + O(1)
c = 2, r = 1, f(n)=n^0, \log_c{r} = 0
n^{\log_c{r}} = n^0 \approx f(n)=n^0 -> ⓑ에 해당함
\therefore\;\; T(n) = O(f(n) \cdot \log{n}) = O(\log{n})
이 경우 Recursion tree는 선형 트리(자식 노드가 하나인 트리)가 됨

Ex) T(n) = 4T(\frac{n}{2}) + O(n)
c = 2, r = 4, f(n)=n^1, \log_c{r} = 2
n^{\log_c{r}} = n^2 > f(n)=n^1 -> ⓒ에 해당함
\therefore\;\; T(n) = O(n^{\log_c{r}}) = O(n^2)
Ex) T(n) = 3T(\frac{n}{2}) + O(n)
c = 2, r = 3, f(n)=n^1, 1 < \log_c{r} = \log_2{3} = 1.5849625< 2
n^{\log_c{r}} = n^{\log_2{3}} > f(n)=n^1 -> ⓒ에 해당함
\therefore\;\; T(n) = O(n^{\log_2{3}}) = O(n^{1.5849625})
Ex) T(n) = T(\frac{n}{3}) + O(n^2)
c = 3, r = 1, f(n)=n^2, \log_c{r} = 0
n^{\log_c{r}} = n^{0} < f(n)=n^2 -> ⓐ에 해당함
\therefore\;\; T(n) = O(f(n)) = O(n^2)
Ex) T(n) = 2T(\frac{n}{2}) + O(n\log{n})
c = 3, r = 1, f(n)=n^2, \log_c{ r} = 0
n^{\log_c{r}} = n^{0}와 f(n)=n^2는 지수로 비교 불가 -> ⓑ에 해당함
\therefore\;\; T(n) = O(f(n)\cdot\log{n}) = O(n\log^2{n})
Ex) T(n) = T(\frac{n}{3}) + T(\frac{2n}{3}) + O(n)
이 경우에는 Master theorem을 적용할 수 없다: T(n) = rT(n/c) + O(f(n)) 꼴이어야 함
-> Recursion tree로 분석
Ex) T(n) = T(n - 2) + T(2) + O(n)
마찬가지로 Master theorem을 적용할 수 없다. 그냥 해도 됨
T(n) = T(n - 2) + cn + O(n): 상수에 대한 T 값은 상수
T(n) = T(n-4) + c(n - 2) + cn
= O(n^2)
재귀 알고리즘에 대한 시간복잡도 분석
알고리즘 명세(슈도코드든 뭐든)를 보고
- subproblem의 size는 어떻게 줄어드는가?
- 몇 번 recursive call 하는가?
- non-recursive work에서 연산의 횟수는 몇 번인가?
를 파악한다.
그리고 점화식을 푼다
- Master theorem
- Recursion tree
- Induction
- 저번에 풀었던 거네?
Mergesort

recursive call: \frac{n}{2} 크기의 subproblem을 2번
non-recursive: O(n)
-> T(n) = 2T(\frac{n}{2}) + O(n)
\therefore\;\; T(n) = O(n\log{n})
Max
max(A[0..n-1]
if\; n=1\; return\; A[0]
mid\; \leftarrow\; n/2
m1\; \leftarrow\; max(A[0..mid])
m2\; \leftarrow\; max(A[mid+1..n-1])
if\; m1>m2
\;\;\;\;then\;return\;m1
else\;return\;m2
T(n) = 2T(\frac{n}{2}) + O(1) = O(n)
Sum
sum(n)
if\; n=1\; return\; 1
if\; n\; is\; even\; then
\;\;\;\;return\; 2*sum3(n/2) + (n/2)^2
else
\;\;\;\;return 2*sum((n-1)/2)+((n-1)/2)^2 + n
T(n) = T(\frac{n}{2}) + O(1) = O(\log{n})
'강의노트 > 알고리듬' 카테고리의 다른 글
[알고리듬] 6주차: Backtracking (0) | 2021.04.07 |
---|---|
[알고리듬] 5주차: Divide-and-Conquer (0) | 2021.03.31 |
[알고리듬] 3주차: Homework#1 (0) | 2021.03.21 |
[알고리듬] 3주차: Recursion - Mergesort, Quicksort (0) | 2021.03.17 |
[알고리듬] 2주차: 정렬 (0) | 2021.03.15 |