Skip to content
Kang Log
Go back

Chapter 4. Divide-and-Conquer

개요

Chapter 4는 divide-and-conquer를 실제 알고리즘 설계와 recurrence 분석의 중심 도구로 확장한다. Chapter 2의 merge sort에서 본 Divide -> Conquer -> Combine 흐름을 다시 확인한 뒤, maximum-subarray problem, matrix multiplication, Strassen’s algorithm, substitution method, recursion-tree method, master method, master theorem proof까지 이어간다.

Divide-and-conquer algorithm은 recursive case와 base case를 함께 가진다. Recursive case에서는 문제를 같은 종류의 더 작은 subproblems로 나누고, subproblems를 재귀적으로 풀고, 결과를 combine한다. Base case에서는 subproblem이 충분히 작아져 더 이상 recursion하지 않고 straightforward하게 해결한다. 때때로 원래 문제와 정확히 같은 형태가 아닌 보조 subproblem을 풀어야 하는데, CLRS는 이를 보통 combine step의 일부로 본다.

핵심 개념

용어핵심 의미검색 키워드
divide-and-conquer문제를 작은 subproblems로 나누고, 재귀적으로 풀고, 결합하는 설계법divide, conquer, combine
recurrence함수 값을 smaller inputs에 대한 자기 자신으로 표현하는 equation/inequalityrecurrence
maximum-subarray problemcontiguous subarray 중 sum이 최대인 nonempty subarray를 찾는 문제maximum subarray, contiguous subarray
crossing subarraymidpoint를 가로질러 왼쪽 suffix와 오른쪽 prefix를 결합한 subarraycrossing midpoint
Strassen’s algorithm8번의 half-size matrix multiplication을 7번으로 줄이는 행렬 곱셈 알고리즘Strassen, matrix multiplication
substitution method답을 guess한 뒤 mathematical induction으로 bound를 증명하는 recurrence 풀이법substitution method
recursion treerecurrence를 recursion level별 cost tree로 펼쳐 summation으로 푸는 방법recursion-tree method
master methodT(n)=aT(n/b)+f(n)T(n) = aT(n/b) + f(n) 꼴 recurrence를 세 cases로 푸는 cookbook methodmaster method, master theorem

세부 정리

Recurrences and technicalities

Recurrence는 divide-and-conquer와 자연스럽게 따라다닌다. 어떤 algorithm이 size nn 문제를 더 작은 input에 대한 recursive calls로 풀면, running time T(n)T(n)도 더 작은 크기의 T()T(\ldots)를 포함하는 식으로 표현된다. 예를 들어 merge sort의 worst-case running time은 다음 recurrence로 표현된다.

T(n)=Θ(1)ifn=1T(n)=2T(n/2)+Θ(n)ifn>1\begin{aligned} T(n) &= \Theta(1) if n = 1 \\ T(n) &= 2T(n/2) + \Theta(n) if n > 1 \end{aligned}

Recurrence는 다양한 형태를 가질 수 있다. Unequal split이면 T(n)=T(2n/3)+T(n/3)+Θ(n)T(n) = T(2n/3) + T(n/3) + \Theta(n)처럼 되고, recursive linear search처럼 input을 하나만 줄이면 T(n)=T(n1)+Θ(1)T(n) = T(n - 1) + \Theta(1)이 된다.

Chapter 4는 recurrence를 푸는 세 방법을 제시한다.

방법핵심 아이디어장점
substitution methodbound를 guess하고 induction으로 증명엄밀한 증명에 좋다
recursion-tree methodlevel별 cost를 tree로 펼쳐 summation으로 계산좋은 guess를 얻기 쉽다
master methodT(n)=aT(n/b)+f(n)T(n) = aT(n/b) + f(n) 꼴을 세 cases로 바로 판정빠르고 반복 사용하기 좋다

Recurrence를 쓸 때는 floors, ceilings, boundary conditions를 종종 생략한다. 예를 들어 실제 merge sort는 odd nn에서 T(n/2)+T(n/2)+Θ(n)T(\lceil n/2 \rceil) + T(\lfloor n/2 \rfloor) + \Theta(n)이지만, 보통 2T(n/2)+Θ(n)2T(n/2) + \Theta(n)으로 쓴다. T(1)T(1) 같은 작은 input의 정확한 값도 생략하는데, 이는 보통 exact solution만 constant factor만큼 바꾸고 asymptotic order는 바꾸지 않기 때문이다. 다만 이런 technicalities가 항상 무해한 것은 아니므로, 어떤 경우에 영향이 없는지 판단하는 감각이 필요하다.

4.1 The maximum-subarray problem

주식 매매 예시에서 maximum subarray로 변환하기

Maximum-subarray problem은 숫자 array에서 sum이 가장 큰 nonempty contiguous subarray를 찾는 문제다. CLRS는 Volatile Chemical Corporation 주식 예시로 시작한다. 미래 가격을 모두 안다고 할 때, 한 번 사고 나중에 한 번 팔아 profit을 최대화하고 싶다.

Figure 4.1은 17일 동안의 stock price와 daily change를 보여준다. 단순히 lowest price에 사고 highest price에 팔면 될 것 같지만, highest price가 lowest price보다 먼저 오면 불가능하다.

Figure 4.1 Figure 4.1 · PDF p. 89 · 주식 가격과 전일 대비 change를 함께 보여 주는 maximum-subarray 예시

Figure 4.2는 maximum profit이 overall lowest price에서 시작하지도, overall highest price에서 끝나지도 않을 수 있음을 보여준다. 즉 문제는 단순히 전역 최소/최대 가격을 찾는 문제가 아니다.

Figure 4.2 Figure 4.2 · PDF p. 90 · maximum profit이 global minimum/maximum price 쌍으로 결정되지 않는 반례

핵심 변환은 price 자체가 아니라 daily change를 array AA로 보는 것이다. Day ii의 change는 day i1i-1 종료 가격과 day ii 종료 가격의 차이다. 어떤 기간에 사고팔아 얻는 profit은 그 기간의 daily changes를 더한 값과 같다. 따라서 maximum profit은 daily-change array에서 maximum-sum contiguous subarray를 찾는 문제로 바뀐다.

Figure 4.3의 array에서는 A[8..11]=<18,20,7,12>A[8..11] = <18, 20, -7, 12>의 sum이 43으로 최대다. 이는 day 7 이후에 사고 day 11 이후에 팔면 주당 43달러 profit을 얻는다는 뜻이다.

Figure 4.3 Figure 4.3 · PDF p. 91 · daily change array에서 A[8..11]A[8..11]이 maximum subarray가 되는 예

Brute force와 문제의 성격

Brute-force solution은 모든 buy/sell date pair, 또는 모든 contiguous subarray를 검사한다. nn일에 대해 가능한 pair는 Θ(n2)\Theta(n^{2})개이고, subarray sums를 이전 계산값을 이용해 O(1)O(1)에 갱신하더라도 전체는 Θ(n2)\Theta(n^{2}) time이다. 목표는 이를 o(n2)o(n^{2})로 개선하는 것이다.

이 문제는 array에 negative numbers가 있을 때만 흥미롭다. 모든 entries가 nonnegative라면 전체 array가 maximum subarray이기 때문이다. 또한 maximum subarray가 여러 개 있을 수 있으므로 원문은 “the” maximum subarray보다 “a” maximum subarray라고 표현한다.

Divide-and-conquer 구조

A[low..high]A[low..high]의 maximum subarray를 찾는다고 하자. Midpoint midmid를 기준으로 보면 어떤 contiguous subarray A[i..j]A[i..j]도 정확히 세 위치 중 하나에 속한다.

경우조건해결 방식
entirely in leftlowijmidlow \le i \le j \le midA[low..mid]A[low..mid]에서 recursive solve
entirely in rightmid<ijhighmid < i \le j \le highA[mid+1..high]A[mid+1..high]에서 recursive solve
crossing midpointlowimid<jhighlow \le i \le mid < j \le high왼쪽 suffix와 오른쪽 prefix를 linear scan으로 결합

Figure 4.4는 이 세 위치와 crossing subarray의 구조를 보여준다. Crossing subarray는 반드시 A[i..mid]A[i..mid]A[mid+1..j]A[mid+1..j]의 결합이다.

Figure 4.4 Figure 4.4 · PDF p. 92 · maximum subarray가 left, right, crossing midpoint 중 하나에 속한다는 divide-and-conquer 구조

FIND-MAX-CROSSING-SUBARRAY

Crossing maximum subarray는 원래 problem의 smaller instance가 아니다. “반드시 midpoint를 crossing해야 한다”는 추가 제약이 있기 때문이다. 그래서 CLRS는 이를 combine step의 일부로 취급한다.

방법은 간단하다. Midpoint에서 왼쪽으로 내려가며 A[i..mid]A[i..mid] 중 최대 sum suffix를 찾고, mid+1mid+1에서 오른쪽으로 올라가며 A[mid+1..j]A[mid+1..j] 중 최대 sum prefix를 찾는다. 둘을 붙이면 maximum subarray crossing midpoint가 된다.

FIND-MAX-CROSSING-SUBARRAY(A, low, mid, high)
1  left-sum = -∞
2  sum = 0
3  for i = mid downto low
4      sum = sum + A[i]
5      if sum > left-sum
6          left-sum = sum
7          max-left = i
8  right-sum = -∞
9  sum = 0
10 for j = mid + 1 to high
11     sum = sum + A[j]
12     if sum > right-sum
13         right-sum = sum
14         max-right = j
15 return (max-left, max-right, left-sum + right-sum)

두 loops의 iteration 수는 (midlow+1)+(highmid)=highlow+1=n(mid - low + 1) + (high - mid) = high - low + 1 = n이므로 running time은 Θ(n)\Theta(n)이다.

FIND-MAXIMUM-SUBARRAY

전체 recursive algorithm은 left, right, crossing 세 후보를 구한 뒤 sum이 가장 큰 tuple을 반환한다.

FIND-MAXIMUM-SUBARRAY(A, low, high)
1  if high == low
2      return (low, high, A[low])
3  else mid = floor((low + high) / 2)
4      (left-low, left-high, left-sum) =
           FIND-MAXIMUM-SUBARRAY(A, low, mid)
5      (right-low, right-high, right-sum) =
           FIND-MAXIMUM-SUBARRAY(A, mid + 1, high)
6      (cross-low, cross-high, cross-sum) =
           FIND-MAX-CROSSING-SUBARRAY(A, low, mid, high)
7      return the tuple with the largest sum among left, right, crossing

Base case는 one element subarray다. 이때 가능한 nonempty subarray는 자기 자신뿐이므로 (low, high, A[low])를 반환한다. Recursive case에서는 두 smaller instances를 conquer하고, crossing case를 combine step에서 계산한다.

Running time

원문은 분석을 단순하게 하기 위해 nn이 power of 2라고 가정한다. Base case는 constant time이다.

T(1)=Θ(1)T(1) = \Theta(1)

Recursive case에서는 left/right 두 subproblems가 각각 size n/2n/2이고, crossing subarray 계산이 Θ(n)\Theta(n)이다. 나머지 comparisons와 tuple returns는 Θ(1)\Theta(1)이다.

T(n)=2T(n/2)+Θ(n)T(n) = 2T(n/2) + \Theta(n)

이 recurrence는 merge sort와 동일하며, master method 또는 Figure 2.5식 recursion tree로 T(n)=Θ(nlgn)T(n) = \Theta(n \lg n)을 얻는다. 따라서 divide-and-conquer maximum-subarray algorithm은 brute-force Θ(n2)\Theta(n^{2})보다 asymptotically faster하다.

단, 이것이 이 문제의 최선은 아니다. Exercise 4.1-5는 왼쪽에서 오른쪽으로 훑으며 “지금까지 본 maximum subarray”와 “현재 index에서 끝나는 maximum subarray”를 유지하는 nonrecursive linear-time algorithm을 유도한다. 즉 maximum-subarray problem은 divide-and-conquer의 힘을 보여 주는 동시에, 문제 구조를 더 깊게 이용하면 Θ(n)\Theta(n)까지 갈 수 있음을 보여준다.

4.2 Strassen’s algorithm for matrix multiplication

Straightforward matrix multiplication

n×nn \times n matrices A=(aij)A = (a_{ij}), B=(bij)B = (b_{ij})의 product C=ABC = A \cdot B에서 entry cijc_{ij}는 다음과 같다.

cij=k=1naikbkjc_{ij} = \sum_{k=1}^{n} a_{ik} \cdot b_{kj}

cijc_{ij}nn개의 product sum이고, entries가 n2n^{2}개 있으므로 straightforward algorithm은 triply nested loops를 가진다.

SQUARE-MATRIX-MULTIPLY(A, B)
1  n = A.rows
2  let C be a new n x n matrix
3  for i = 1 to n
4      for j = 1 to n
5          c_ij = 0
6          for k = 1 to n
7              c_ij = c_ij + a_ik · b_kj
8  return C

세 loops가 정확히 nn번씩 돌고 line 7이 constant time이므로 running time은 Θ(n3)\Theta(n^{3})이다. 직관적으로는 matrix multiplication definition 자체가 n3n^{3}개의 scalar multiplications를 요구하는 것처럼 보이지만, Strassen’s algorithm은 이를 깨고 o(n3)o(n^{3}) time이 가능함을 보여준다.

Simple divide-and-conquer matrix multiplication

먼저 nn이 power of 2라고 가정하고, 각 n×nn \times n matrix를 네 개의 n/2×n/2n/2 \times n/2 submatrices로 나눈다.

A=[A11A12]B=[B11B12]C=[C11C12][ A21 A22 ] [ B21 B22 ] [ C21 C22 ]\begin{aligned} A &= [ A11 A12 ] B = [ B11 B12 ] C = [ C11 C12 ] \\ \text{[ A21 A22 ] [ B21 B22 ] [ C21 C22 ]} \end{aligned}

그러면 C=ABC = A \cdot B는 네 식으로 나뉜다.

C11=A11B11+A12B21C12=A11B12+A12B22C21=A21B11+A22B21C22=A21B12+A22B22\begin{aligned} C11 &= A11\cdot B11 + A12\cdot B21 \\ C12 &= A11\cdot B12 + A12\cdot B22 \\ C21 &= A21\cdot B11 + A22\cdot B21 \\ C22 &= A21\cdot B12 + A22\cdot B22 \end{aligned}

CijC_{ij}는 two multiplications of n/2×n/2n/2 \times n/2 matrices와 one matrix addition을 요구한다. 따라서 전체적으로 half-size matrix multiplication이 8번 필요하다.

SQUARE-MATRIX-MULTIPLY-RECURSIVE(A, B)
1  n = A.rows
2  let C be a new n x n matrix
3  if n == 1
4      c11 = a11 · b11
5  else partition A, B, C into n/2 x n/2 submatrices
6      C11 = recursive(A11, B11) + recursive(A12, B21)
7      C12 = recursive(A11, B12) + recursive(A12, B22)
8      C21 = recursive(A21, B11) + recursive(A22, B21)
9      C22 = recursive(A21, B12) + recursive(A22, B22)
10 return C

구현상 중요한 subtleness는 partition이다. 실제로 12개의 n/2 x n/2 matrices를 복사하면 Θ(n2)\Theta(n^{2}) 시간이 들지만, submatrix를 original matrix의 row/column index range로 표현하면 line 5는 Θ(1)\Theta(1)로 처리할 수 있다. 다만 partition copying을 해도 recurrence의 additive term이 Θ(n2)\Theta(n^{2})로 남으므로 overall asymptotic order는 바뀌지 않는다.

Running time recurrence는 다음과 같다.

T(1)=Θ(1)T(n)=8T(n/2)+Θ(n2)\begin{aligned} T(1) &= \Theta(1) \\ T(n) &= 8T(n/2) + \Theta(n^{2}) \end{aligned}

Master method로 풀면 T(n)=Θ(n3)T(n) = \Theta(n^{3})이다. 즉 naive divide-and-conquer formulation은 straightforward triple loop보다 asymptotically 빠르지 않다.

여기서 중요한 주의점이 나온다. Θ-notation은 ordinary multiplicative constant를 숨기지만, recursive calls의 개수인 8T(n/2)8T(n/2)의 8은 숨기면 안 된다. 이 8은 recursion tree의 branching factor이고, 각 level의 node 수를 결정한다. 8T(n/2)8T(n/2)를 단순히 T(n/2)T(n/2)처럼 쓰면 recursion tree 자체가 달라진다.

Strassen’s method: 8 multiplications를 7로 줄이기

Strassen’s method의 핵심은 recursion tree를 “덜 bushy하게” 만드는 것이다. Simple divide-and-conquer는 half-size multiplication을 8번 하지만, Strassen은 이를 7번으로 줄인다. 대신 n/2×n/2n/2 \times n/2 matrix additions/subtractions를 constant number만큼 더 수행한다. Additions는 전체 recurrence에서 Θ(n2)\Theta(n^{2}) additive work로 남고, recursive multiplication 수가 8에서 7로 줄어드는 효과가 asymptotic running time을 낮춘다.

Strassen’s algorithm은 네 단계로 볼 수 있다.

단계내용비용
1AA, BB, CCn/2×n/2n/2 \times n/2 submatrices로 나눈다Θ(1)\Theta(1) by index calculation
2S1, ..., S10을 submatrices의 sums/differences로 만든다Θ(n2)\Theta(n^{2})
3P1, ..., P7이라는 7개의 recursive matrix products를 계산한다7T(n/2)7T(n/2)
4P matrices를 더하고 빼서 C11, C12, C21, C22를 만든다Θ(n2)\Theta(n^{2})

따라서 recurrence는 다음과 같다.

T(1)=Θ(1)T(n)=7T(n/2)+Θ(n2)\begin{aligned} T(1) &= \Theta(1) \\ T(n) &= 7T(n/2) + \Theta(n^{2}) \end{aligned}

Master method로 이 recurrence의 solution은 T(n)=Θ(nlg7)T(n) = \Theta(n^{\lg 7})이다. lg7\lg 7은 2.80과 2.81 사이이므로 O(n2.81)O(n^{2.81})이라고도 표현한다. 이는 Θ(n3)\Theta(n^{3})보다 asymptotically faster하다.

Strassen의 S matrices와 P products

Step 2에서 만드는 10개 matrices는 다음과 같다.

S1=B12B22S2=A11+A12S3=A21+A22S4=B21B11S5=A11+A22S6=B11+B22S7=A12A22S8=B21+B22S9=A11A21S10=B11+B12\begin{aligned} \text{S1} &= B12 - B22 \\ \text{S2} &= A11 + A12 \\ \text{S3} &= A21 + A22 \\ \text{S4} &= B21 - B11 \\ \text{S5} &= A11 + A22 \\ \text{S6} &= B11 + B22 \\ \text{S7} &= A12 - A22 \\ \text{S8} &= B21 + B22 \\ \text{S9} &= A11 - A21 \\ S10 &= B11 + B12 \end{aligned}

Step 3에서는 recursive multiplication을 정확히 7번만 수행한다.

P1=A11S1P2=S2B22P3=S3B11P4=A22S4P5=S5S6P6=S7S8P7=S9S10\begin{aligned} P1 &= A11 \cdot S1 \\ P2 &= S2 \cdot B22 \\ P3 &= S3 \cdot B11 \\ P4 &= A22 \cdot S4 \\ P5 &= S5 \cdot S6 \\ P6 &= S7 \cdot S8 \\ P7 &= S9 \cdot S10 \end{aligned}

Step 4에서 네 output blocks를 다음처럼 만든다.

C11=P5+P4P2+P6C12=P1+P2C21=P3+P4C22=P5+P1P3P7\begin{aligned} C11 &= P5 + P4 - P2 + P6 \\ C12 &= P1 + P2 \\ C21 &= P3 + P4 \\ C22 &= P5 + P1 - P3 - P7 \end{aligned}

이 식들이 맞는 이유는 각 Pi를 원래 Aij, Bij terms로 전개하면 불필요한 항들이 cancel되고, simple block multiplication의 네 식 C11, C12, C21, C22가 남기 때문이다.

Output block전개 후 남는 항원래 block multiplication 식
C11A11·B11 + A12·B21C11=A11B11+A12B21C11 = A11\cdot B11 + A12\cdot B21
C12A11·B12 + A12·B22C12=A11B12+A12B22C12 = A11\cdot B12 + A12\cdot B22
C21A21·B11 + A22·B21C21=A21B11+A22B21C21 = A21\cdot B11 + A22\cdot B21
C22A21·B12 + A22·B22C22=A21B12+A22B22C22 = A21\cdot B12 + A22\cdot B22

Trade-off와 실용적 의미

Strassen은 multiplication 하나를 없애는 대신 additions/subtractions를 늘린다. 일반 산술에서 multiplication이 더 “비싸다”는 단순 직관 때문만은 아니다. Divide-and-conquer recurrence에서 recursive multiplications 수가 branching factor가 되기 때문에, 8에서 7로 줄이는 것이 exponent를 33에서 lg7\lg 7로 낮춘다.

다만 원문은 practical aspects를 chapter notes에서 다시 논의한다고 예고한다. Strassen은 asymptotically faster하지만 constant factors, memory use, numerical stability, crossover point 때문에 실제 구현에서는 항상 무조건 좋은 선택이 아니다.

4.3 The substitution method for solving recurrences

기본 절차

Substitution method는 recurrence solution의 form을 guess하고, 그 guess를 mathematical induction으로 증명하는 방법이다.

단계의미
1. Guess the formrecurrence의 solution이 O()O(\ldots), Ω()\Omega(\ldots), Θ()\Theta(\ldots) 중 어떤 꼴인지 추측한다
2. Prove by inductionsmaller values에 inductive hypothesis를 적용해 constants를 찾고 bound가 성립함을 보인다

“Substitution”이라는 이름은 inductive hypothesis로 얻은 smaller input bound를 recurrence의 T()T(\ldots) 자리에 대입하기 때문이다. 강력하지만, answer form을 추측할 수 있어야 쓸 수 있다.

예: T(n)=2T(n/2)+nT(n) = 2T(\lfloor n/2 \rfloor) + n

원문은 다음 recurrence의 upper bound를 보인다.

T(n)=2T(n/2)+nT(n) = 2T(\lfloor n/2 \rfloor) + n

Guess는 T(n)=O(nlgn)T(n) = O(n \lg n)이다. 이를 증명하려면 적절한 constant c>0c > 0에 대해 T(n)cnlgnT(n) \le c n \lg n을 보여야 한다. Inductive hypothesis로 모든 positive m<nm < n에 대해 bound가 성립한다고 가정하면, 특히 m=n/2m = \lfloor n/2 \rfloor에 대해

T(n/2)cn/2lg(n/2)T(\lfloor n/2 \rfloor) \le c\lfloor n/2 \rfloor \lg(\lfloor n/2 \rfloor)

이다. 이를 recurrence에 대입한다.

T(n)2(cn/2lg(n/2))+ncnlg(n/2)+n=cnlgncn+ncnlgnifc1\begin{aligned} T(n) &\le 2(c\lfloor n/2 \rfloor \lg(\lfloor n/2 \rfloor)) + n \\ &\le c n \lg(n/2) + n \\ &= c n \lg n - c n + n \\ &\le c n \lg n if c \ge 1 \end{aligned}

이로써 inductive step은 성립한다.

Boundary condition과 induction base case

Substitution proof에서 base case는 조심해야 한다. 위 recurrence에서 T(1)=1T(1) = 1이라고 하면 T(1)c1lg1=0T(1) \le c \cdot 1 \cdot \lg 1 = 0이 되어 실패한다. 이는 guess가 틀렸다는 뜻이 아니라, asymptotic bound가 모든 n1n \ge 1에서 성립할 필요는 없다는 뜻이다.

OO notation은 nn0n \ge n_0 이후만 요구한다. 원문은 recurrence base case T(1)T(1)과 inductive proof의 base cases를 구분한다. 예를 들어 n0=2n_0 = 2로 두고 T(2)T(2), T(3)T(3)을 induction base cases로 삼으면, n>3n > 3에서는 recurrence가 직접 T(1)T(1)에 의존하지 않는다. T(2)=4T(2) = 4, T(3)=5T(3) = 5를 만족하도록 c2c \ge 2 정도로 충분히 크게 잡으면 proof가 완성된다.

즉 recurrence의 boundary condition과 induction proof의 base cases는 반드시 같을 필요가 없다. Asymptotic proof에서는 작은 nn 몇 개를 적절한 constants로 흡수할 수 있다.

Making a good guess

Guess를 만드는 일반 알고리즘은 없다. 그러나 몇 가지 heuristic이 있다.

Heuristic설명
비슷한 recurrence와 비교T(n)=2T(n/2+17)+nT(n) = 2T(\lfloor n/2 \rfloor + 17) + n2T(n/2)+n2T(n/2) + n과 거의 같으므로 O(nlgn)O(n \lg n)을 guess할 수 있다
loose bounds에서 좁히기먼저 Ω(n)\Omega(n)O(n2)O(n^{2}) 같은 넓은 bound를 잡고 점차 tight하게 만든다
recursion tree 사용Section 4.4의 recursion-tree method로 level costs를 보고 guess를 만든다

+17+17 같은 constant perturbation은 large nn에서 half split의 본질을 크게 바꾸지 않는다. 이런 직관을 substitution proof로 확인할 수 있다.

Subtleties: stronger inductive hypothesis가 필요할 때

때때로 correct asymptotic bound를 guess했는데도 induction이 막힌다. 이때는 guess가 틀린 것이 아니라 inductive hypothesis가 충분히 강하지 않은 경우가 많다.

예를 들어

T(n)=T(n/2)+T(n/2)+1T(n) = T(\lfloor n/2 \rfloor) + T(\lceil n/2 \rceil) + 1

에 대해 T(n)=O(n)T(n) = O(n)을 guess하고 T(n)cnT(n) \le cn을 증명하려 하면,

T(n)cn/2+cn/2+1=cn+1\begin{aligned} T(n) &\le c\lfloor n/2 \rfloor + c\lceil n/2 \rceil + 1 \\ &= cn + 1 \end{aligned}

이 되어 T(n)cnT(n) \le cn을 얻지 못한다. 하지만 bound 자체는 맞다. 해결법은 lower-order term을 subtract한 stronger hypothesis를 세우는 것이다.

T(n)cndT(n) \le cn - d

라고 guess하면,

T(n)(cn/2d)+(cn/2d)+1=cn2d+1cndifd1\begin{aligned} T(n) &\le (c\lfloor n/2 \rfloor - d) + (c\lceil n/2 \rceil - d) + 1 \\ &= cn - 2d + 1 \\ &\le cn - d if d \ge 1 \end{aligned}

이 된다. Upper bound proof에서 더 약한 cncn보다 더 강한 cndcn - d가 오히려 증명하기 쉬워질 수 있다. Recursive terms가 여러 개이면 lower-order term을 recursive term마다 한 번씩 subtract할 수 있기 때문이다.

Avoiding pitfalls

Substitution method에서 흔한 오류는 asymptotic notation으로 induction의 exact target을 흐리는 것이다. 예를 들어 위 T(n)=2T(n/2)+nT(n) = 2T(\lfloor n/2 \rfloor) + n에 대해 T(n)=O(n)T(n) = O(n)이라고 잘못 증명하려 하면서

T(n)2(cn/2)+ncn+n=O(n)wrong\begin{aligned} T(n) &\le 2(c\lfloor n/2 \rfloor) + n \\ &\le cn + n \\ &= O(n) wrong \end{aligned}

이라고 쓰면 안 된다. Induction으로 증명해야 할 것은 T(n)cnT(n) \le cn이라는 exact form이지, 중간에 다시 O(n)O(n)으로 뭉개는 것이 아니다. cn+ncn + nO(n)O(n)이지만 cncn 이하는 아니다.

Changing variables

Algebraic manipulation으로 낯선 recurrence를 익숙한 꼴로 바꿀 수 있다. 예를 들어

T(n)=2T(n)+lgnT(n) = 2T(\sqrt{n}) + \lg n

에서 m=lgnm = \lg n이라고 두면 n=2mn = 2^{m}, n=2m/2\sqrt{n} = 2^{m/2}이므로

T(2m)=2T(2m/2)+mT(2^{m}) = 2T(2^{m/2}) + m

새 함수 S(m)=T(2m)S(m) = T(2^{m})를 정의하면

S(m)=2S(m/2)+mS(m) = 2S(m/2) + m

이 된다. 이는 앞서 본 recurrence와 같은 꼴이므로 S(m)=O(mlgm)S(m) = O(m \lg m)이다. 다시 m=lgnm = \lg n을 대입하면

T(n)=O(lgnlglgn)T(n) = O(\lg n \cdot \lg \lg n)

을 얻는다. 이 방법은 square root, logarithm, exponent가 섞인 recurrence를 다룰 때 특히 유용하다.

4.4 The recursion-tree method for solving recurrences

Recursion tree의 역할

Recursion-tree method는 recurrence를 tree로 펼쳐 각 recursive invocation의 cost를 node로 표현하는 방법이다. 각 level의 node costs를 합해 per-level cost를 얻고, 모든 levels의 cost를 다시 합해 total cost를 구한다.

이 방법은 substitution method보다 direct proof로 쓰기에는 조심할 점이 많지만, 좋은 guess를 만드는 데 매우 유용하다. 약간의 floors/ceilings 생략이나 power-of assumption 같은 sloppiness를 허용할 수 있고, 이후 substitution method로 guess를 검증하면 된다. 매우 조심스럽게 accounting하면 recursion tree 자체가 proof가 될 수도 있으며, Section 4.6의 master theorem proof가 이런 방향이다.

예 1: T(n)=3T(n/4)+cn2T(n) = 3T(n/4) + cn^{2}

원문은 T(n)=3T(n/4)+Θ(n2)T(n) = 3T(\lfloor n/4 \rfloor) + \Theta(n^{2})의 upper bound를 찾기 위해 floors를 생략하고 다음 recurrence를 분석한다.

T(n)=3T(n/4)+cn2T(n) = 3T(n/4) + c n^{2}

Figure 4.5는 이 recurrence의 recursion tree를 보여준다. Root cost는 cn2cn^{2}이고, 각 node는 세 children을 가지며 subproblem size는 매 level마다 1/4로 줄어든다.

Figure 4.5 Figure 4.5 · PDF p. 110 · T(n)=3T(n/4)+cn2T(n) = 3T(n/4) + cn^{2}의 recursion tree와 level별 비용 감소

Depth ii의 subproblem size는 n/4in/4^{i}다. n/4i=1n/4^{i} = 1이 될 때 i=log4ni = \log_{4} n이므로 tree height는 log4n\log_4 n이고, level 수는 log4n+1\log_4 n + 1이다.

Depth ii의 node 수는 3i3^{i}이고, 각 node cost는 c(n/4i)2c(n/4^{i})^{2}다. 따라서 depth ii의 total cost는

3ic(n/4i)2=(3/16)icn23^{i} \cdot c(n/4^{i})^{2} = (3/16)^{i} c n^{2}

이다. Bottom level에는 3log4n=nlog433^{\log_{4} n} = n^{\log_{4} 3} leaves가 있고, 각 leaf cost는 constant이므로 leaf total cost는 Θ(nlog43)\Theta(n^{\log_{4} 3})이다.

Internal levels의 cost는 decreasing geometric series다.

i=0log4n1(3/16)icn2<i=0(3/16)icn2=(16/13)cn2\begin{aligned} \sum_{i=0}^{\log_{4} n - 1} (3/16)^{i} c n^{2} < \sum_{i=0}^{\infty} (3/16)^{i} c n^{2} \\ &= (16/13) c n^{2} \end{aligned}

따라서 total cost는 O(n2)O(n^{2})이다. Root cost cn2cn^{2}가 total cost의 constant fraction을 차지하므로 root dominates the tree라고 볼 수 있다. 또한 첫 recursive expansion 자체에 Θ(n2)\Theta(n^{2}) cost가 있으므로 lower bound도 Ω(n2)\Omega(n^{2})이고, 결국 tight bound는 Θ(n2)\Theta(n^{2})다.

Substitution으로 upper bound를 확인하면 다음과 같다.

T(n)3T(n/4)+cn23dn/42+cn2(3/16)dn2+cn2dn2ifd(16/13)c\begin{aligned} T(n) &\le 3T(\lfloor n/4 \rfloor) + c n^{2} \\ &\le 3d\lfloor n/4 \rfloor^{2} + c n^{2} \\ &\le (3/16)d n^{2} + c n^{2} \\ &\le d n^{2} if d \ge (16/13)c \end{aligned}

예 2: T(n)=T(n/3)+T(2n/3)+cnT(n) = T(n/3) + T(2n/3) + cn

Figure 4.6은 unequal split recurrence를 보여준다.

T(n)=T(n/3)+T(2n/3)+cnT(n) = T(n/3) + T(2n/3) + cn

Figure 4.6 Figure 4.6 · PDF p. 112 · T(n)=T(n/3)+T(2n/3)+cnT(n) = T(n/3) + T(2n/3) + cn의 unequal split recursion tree

상단 levels에서는 각 level의 cost를 합하면 cncn처럼 보인다. 가장 긴 root-to-leaf path는 매번 2n/32n/3 쪽으로 내려가는 path이고,

(2/3)kn=1k=log3/2n(2/3)^{k} n = 1 \Rightarrow k = \log_{3/2} n

이므로 height는 log3/2n\log_{3/2} n이다. 따라서 직관적으로 total cost는 level 수 곱하기 level cost, 즉 O(nlgn)O(n \lg n)일 것이라고 guess한다.

다만 이 tree는 complete binary tree가 아니다. 아래쪽으로 갈수록 subproblem들이 먼저 leaf에 도달해 internal nodes가 빠지므로, 모든 level이 정확히 cncn을 기여하지는 않는다. 그래도 upper-bound guess로는 충분하다.

Substitution으로 T(n)dnlgnT(n) \le d n \lg n을 검증하면:

T(n)T(n/3)+T(2n/3)+cnd(n/3)lg(n/3)+d(2n/3)lg(2n/3)+cn=dnlgndn(lg32/3)+cndnlgn\begin{aligned} T(n) &\le T(n/3) + T(2n/3) + cn \\ &\le d(n/3)\lg(n/3) + d(2n/3)\lg(2n/3) + cn \\ &= d n \lg n - d n(\lg 3 - 2/3) + cn \\ &\le d n \lg n \end{aligned}

마지막 부등식은 dc/(lg32/3)d \ge c / (\lg 3 - 2/3)이면 성립한다. 따라서 O(nlgn)O(n \lg n) upper bound가 검증된다.

Recursion tree를 읽는 순서

Recursion-tree method를 사용할 때는 다음 순서로 보면 좋다.

단계질문
1. Node costsize mm subproblem의 nonrecursive work는 무엇인가?
2. Branching각 node가 몇 개의 recursive children을 만드는가?
3. Size shrinkchild subproblem size는 parent의 몇 배인가?
4. Level costdepth ii의 node 수와 node당 cost를 곱하면 무엇인가?
5. Height/leavesbase case까지 몇 levels가 있고 leaves 총 cost는 얼마인가?
6. Dominant partroot, leaves, 또는 모든 levels 중 어디가 total cost를 지배하는가?
7. Verification얻은 guess를 substitution method로 확인할 수 있는가?

이 흐름은 Section 4.5의 master theorem 세 cases와도 연결된다. 어떤 경우에는 leaves가 지배하고, 어떤 경우에는 모든 levels가 비슷하게 기여하며, 어떤 경우에는 root 근처의 work가 지배한다.

4.5 The master method for solving recurrences

Master method의 대상

Master method는 다음 꼴의 recurrence를 빠르게 푸는 cookbook method다.

T(n)=aT(n/b)+f(n)T(n) = aT(n/b) + f(n)

여기서 a1a \ge 1, b>1b > 1은 constants이고, f(n)f(n)은 asymptotically positive function이다. 이 recurrence는 size nn 문제를 aa개의 subproblems로 나누고, 각 subproblem size가 n/bn/b이며, divide와 combine의 전체 cost가 f(n)f(n)인 divide-and-conquer algorithm을 나타낸다.

예를 들어 Strassen’s algorithm은 a=7a = 7, b=2b = 2, f(n)=Θ(n2)f(n) = \Theta(n^{2})이다.

Floors와 ceilings는 보통 생략한다. 실제로는 T(n/b)T(\lfloor n/b \rfloor) 또는 T(n/b)T(\lceil n/b \rceil)가 되어야 하지만, master theorem은 이런 rounding이 asymptotic behavior를 바꾸지 않음을 보장한다.

Theorem 4.1: Master theorem

Master theorem은 f(n)f(n)nlogban^{\log_{b} a}를 비교한다. 여기서 nlogban^{\log_{b} a}는 recursion leaves 또는 recursive subproblem proliferation이 만들어 내는 기준 성장률이다.

Case조건결과직관
1f(n)=O(nlogbaε)f(n) = O(n^{\log_{b} a - \varepsilon}) for some ε>0\varepsilon > 0T(n)=Θ(nlogba)T(n) = \Theta(n^{\log_{b} a})recursive subproblems 쪽이 지배
2f(n)=Θ(nlogba)f(n) = \Theta(n^{\log_{b} a})T(n)=Θ(nlogbalgn)T(n) = \Theta(n^{\log_{b} a} \lg n)각 recursion level이 비슷하게 기여
3f(n)=Ω(nlogba+ε)f(n) = \Omega(n^{\log_{b} a + \varepsilon}) for some ε>0\varepsilon > 0, and af(n/b)cf(n)a f(n/b) \le c f(n) for some c<1c < 1T(n)=Θ(f(n))T(n) = \Theta(f(n))root/combine work 쪽이 지배

Case 3의 추가 조건 af(n/b)cf(n)a f(n/b) \le c f(n)은 regularity condition이다. 이는 f(n)f(n)이 충분히 규칙적으로 커져서 상위 level의 work가 하위 level들의 total work보다 일정 비율 이상 크다는 뜻이다. 원문은 우리가 만나는 대부분의 polynomially bounded functions에서는 이 조건이 만족된다고 설명한다.

Polynomially smaller/larger 조건

Master theorem의 세 cases는 단순히 f(n)f(n)이 작다/같다/크다만 보지 않는다.

대표적인 gap 예시는 다음 recurrence다.

T(n)=2T(n/2)+nlgnT(n) = 2T(n/2) + n \lg n

여기서 a=2a = 2, b=2b = 2, nlogba=nn^{\log_{b} a} = n, f(n)=nlgnf(n) = n \lg n이다. f(n)f(n)nn보다 asymptotically larger이지만, ratio가 lgn\lg n뿐이라 어떤 nεn^\varepsilon보다도 작다. 따라서 Case 3의 polynomially larger 조건을 만족하지 못하고, master method를 바로 적용할 수 없다.

사용 예시

Master method는 aa, bb, f(n)f(n)을 확인하고 f(n)f(n)nlogban^{\log_{b} a}와 비교하면 된다.

Recurrenceaa, bb, f(n)f(n)비교CaseSolution
T(n)=9T(n/3)+nT(n) = 9T(n/3) + na=9a=9, b=3b=3, f(n)=nf(n)=nnlog39=n2n^{\log_{3} 9}=n^{2}, f(n)f(n) smaller1Θ(n2)\Theta(n^{2})
T(n)=T(2n/3)+1T(n) = T(2n/3) + 1a=1a=1, b=3/2b=3/2, f(n)=1f(n)=1nlog3/21=1n^{\log_{3/2}1}=1, same2Θ(lgn)\Theta(\lg n)
T(n)=3T(n/4)+nlgnT(n) = 3T(n/4) + n \lg na=3a=3, b=4b=4, f(n)=nlgnf(n)=n \lg nnlog43=O(n0.793)n^{\log_{4} 3}=O(n^{0}.793), f(n)f(n) larger and regular3Θ(nlgn)\Theta(n \lg n)

Case 3 regularity check 예시는 다음과 같다.

af(n/b)=3(n/4)lg(n/4)(3/4)nlgn=cf(n),c=3/4<1\begin{aligned} a f(n/b) &= 3 (n/4) \lg(n/4) \\ &\le (3/4) n \lg n \\ &= c f(n), c = 3/4 < 1 \end{aligned}

앞선 알고리즘들의 recurrence 풀기

Maximum-subarray divide-and-conquer와 merge sort의 recurrence는 동일하다.

T(n)=2T(n/2)+Θ(n)T(n) = 2T(n/2) + \Theta(n)

여기서 a=2a = 2, b=2b = 2, nlogba=nn^{\log_{b} a} = n, f(n)=Θ(n)f(n) = \Theta(n)이므로 Case 2다.

T(n)=Θ(nlgn)T(n) = \Theta(n \lg n)

Simple divide-and-conquer matrix multiplication은

T(n)=8T(n/2)+Θ(n2)T(n) = 8T(n/2) + \Theta(n^{2})

이고 a=8a = 8, b=2b = 2, nlog28=n3n^{\log_{2} 8} = n^{3}이다. f(n)=Θ(n2)f(n) = \Theta(n^{2})는 polynomially smaller이므로 Case 1이다.

T(n)=Θ(n3)T(n) = \Theta(n^{3})

Strassen’s algorithm은

T(n)=7T(n/2)+Θ(n2)T(n) = 7T(n/2) + \Theta(n^{2})

이고 a=7a = 7, b=2b = 2, nlog27=nlg7n^{\log_{2} 7} = n^{\lg 7}이다. 2.80<lg7<2.812.80 < \lg 7 < 2.81이고 f(n)=Θ(n2)f(n)=\Theta(n^{2})nlg7n^{\lg 7}보다 polynomially smaller이므로 Case 1이다.

T(n)=Θ(nlg7)=O(n2.81)T(n) = \Theta(n^{\lg 7}) = O(n^{2}.81)

이 결과가 Strassen이 straightforward Θ(n3)\Theta(n^{3}) matrix multiplication보다 asymptotically faster한 이유다.

4.6 Proof of the master theorem

이 절은 master theorem을 적용하는 데 필수는 아니지만, 왜 세 case가 생기는지 설명한다. 증명은 두 단계다. 먼저 nn이 정확히 bb의 거듭제곱(exact powers of bb)일 때 recurrence tree를 분석한다. 그다음 실제 알고리즘처럼 n/b\lfloor n/b \rfloor, n/b\lceil n/b \rceil가 들어가는 모든 양의 정수 nn으로 확장한다.

주의할 점은 제한된 domain에서의 asymptotic notation이다. 예를 들어 어떤 함수가 n=1,2,4,8,n=1,2,4,8,\ldots에서만 O(n)O(n)이어도, 다른 nn에서 n2n^{2}처럼 정의되어 있다면 전체 domain에서는 O(n)O(n)이라고 말할 수 없다. 그래서 proof는 exact powers에서 직관을 얻되, 마지막에 floor/ceiling을 따로 처리한다.

4.6.1 The proof for exact powers

대상 recurrence는 다음 master recurrence다.

T(n)=aT(n/b)+f(n)T(n) = aT(n/b) + f(n)

여기서 a1a \ge 1, b>1b > 1, f(n)f(n)은 nonnegative function이고, 우선 n=bin = b^{i}라고 가정한다. Lemma 4.2는 이 recurrence를 직접 푸는 대신, recursion tree의 level cost 합으로 바꾼다.

Figure 4.7 Figure 4.7 · PDF p. 120 · T(n)=aT(n/b)+f(n)T(n)=aT(n/b)+f(n)이 만드는 complete a-ary recursion tree

tree에서 depth jj에는 node가 aja^{j}개 있고, 각 node의 subproblem size는 n/bjn/b^{j}다. 따라서 depth jj의 internal-node cost는

ajf(n/bj)a^{j} f(n/b^{j})

이다. leaf는 n/bj=1n/b^{j} = 1이 되는 depth, 즉 j=logbnj = \log_{b} n에 있고, leaf 수는

alogbn=nlogbaa^{\log_{b} n} = n^{\log_{b} a}

개다. leaf 하나의 cost는 Θ(1)\Theta(1)이므로 leaf 전체 cost는 Θ(nlogba)\Theta(n^{\log_{b} a})다. 따라서 exact powers에서 전체 시간은

T(n)=Θ(nlogba)+j=0logbn1ajf(n/bj)T(n) = \Theta(n^{\log_{b} a}) + \sum_{j=0}^{\log_{b} n - 1} a^{j} f(n/b^{j})

로 표현된다. 이 식에서 앞 항은 leaves cost, 뒤의 summation은 divide/combine cost의 level별 합이다. master theorem의 세 case는 이 tree에서 비용이 어디에 몰리는지에 대한 분류다.

casetree 관점결과
Case 1f(n)f(n)이 leaf 기준 nlogban^{\log_{b} a}보다 polynomially smaller라서 leaves가 지배Θ(nlogba)\Theta(n^{\log_{b} a})
Case 2각 level의 총 cost가 거의 같아서 level 수만큼 누적Θ(nlogbalgn)\Theta(n^{\log_{b} a} \lg n)
Case 3root 쪽 cost f(n)f(n)이 충분히 커서 위쪽 level이 지배Θ(f(n))\Theta(f(n))

Lemma 4.3은 summation

g(n)=j=0logbn1ajf(n/bj)g(n) = \sum_{j=0}^{\log_{b} n - 1} a^{j} f(n/b^{j})

을 세 상황에서 평가한다.

Case 1에서는 f(n)=O(nlogbaε)f(n)=O(n^{\log_{b} a - \varepsilon})이다. f(n/bj)f(n/b^{j})를 대입하면 level cost가 geometric series 형태가 되고, 전체 internal cost는 O(nlogba)O(n^{\log_{b} a})를 넘지 않는다. leaf cost도 Θ(nlogba)\Theta(n^{\log_{b} a})이므로 최종적으로 T(n)=Θ(nlogba)T(n)=\Theta(n^{\log_{b} a})다.

Case 2에서는 f(n)=Θ(nlogba)f(n)=\Theta(n^{\log_{b} a})이다. 이때 각 level의 cost가 모두 같은 차수다.

aj(n/bj)logba=nlogba(a/blogba)j=nlogba\begin{aligned} a^{j} (n/b^{j})^{\log_{b} a} \\ = n^{\log_{b} a} (a / b^{\log_{b} a})^{j} \\ = n^{\log_{b} a} \end{aligned}

level이 logbn\log_b n개 있으므로 internal cost는 Θ(nlogbalgn)\Theta(n^{\log_{b} a} \lg n)이고, leaf cost보다 크거나 같은 차수라서 전체도 Θ(nlogbalgn)\Theta(n^{\log_{b} a} \lg n)이다.

Case 3에서는 regularity condition

af(n/b)cf(n)forsomeconstantc<1a f(n/b) \le c f(n) for some constant c < 1

이 중요하다. 이 조건은 한 level 아래로 내려갈 때 전체 level cost가 적어도 일정 비율로 줄어든다는 뜻이다. 반복하면

ajf(n/bj)cjf(n)a^{j} f(n/b^{j}) \le c^{j} f(n)

가 되어 internal cost가 decreasing geometric series가 된다.

g(n)f(n)cj+O(1)=O(f(n))g(n) \le f(n) \sum c^{j} + O(1) = O(f(n))

또한 summation의 첫 항이 f(n)f(n)이므로 g(n)=Ω(f(n))g(n)=\Omega(f(n))이고, 결국 g(n)=Θ(f(n))g(n)=\Theta(f(n))이다. 여기에 f(n)=Ω(nlogba+ε)f(n)=\Omega(n^{\log_{b} a+\varepsilon})가 붙으면 leaf cost Θ(nlogba)\Theta(n^{\log_{b} a})f(n)f(n)보다 작아서 T(n)=Θ(f(n))T(n)=\Theta(f(n))가 된다.

4.6.2 Floors and ceilings

실제 recurrence는 보통 subproblem size가 정확히 n/bn/b가 아니라 n/b\lfloor n/b \rfloor 또는 n/b\lceil n/b \rceil다.

T(n)=aT(n/b)+f(n)T(n)=aT(n/b)+f(n)\begin{aligned} T(n) &= aT(\lceil n/b \rceil) + f(n) \\ T(n) &= aT(\lfloor n/b \rfloor) + f(n) \end{aligned}

proof는 exact powers에서 얻은 tree가 floor/ceiling 때문에 크게 망가지지 않음을 보인다. ceiling recurrence의 recursive argument를

n0=nnj=nj1/bforj>0\begin{aligned} n_{0} &= n \\ n_{j} &= \lceil n_{j-1}/b \rceil for j > 0 \end{aligned}

로 두면, xx+1\lceil x \rceil \le x + 1이므로 다음 bound를 얻는다.

nj<n/bj+b/(b1)n_{j} < n/b^{j} + b/(b-1)

j=logbnj = \lfloor \log_{b} n \rfloor이면 nj=O(1)n_{j} = O(1)이므로 tree depth는 여전히 Θ(lgn)\Theta(\lg n)이다. 따라서 ceiling이 있어도 tree는 대략 Figure 4.8처럼 읽을 수 있다.

Figure 4.8 Figure 4.8 · PDF p. 125 · T(n)=aT(n/b)+f(n)T(n)=aT(\lceil n/b \rceil)+f(n)에서 recursive argument njn_{j}를 쓰는 recursion tree

전체 cost는 exact powers의 식과 거의 같은 형태가 된다.

T(n)=Θ(nlogba)+j=0logbn1ajf(nj)T(n) = \Theta(n^{\log_{b} a}) + \sum_{j=0}^{\lfloor \log_{b} n \rfloor - 1} a^{j} f(n_{j})

Case 3에서는 regularity condition을 af(n/b)cf(n)a f(\lceil n/b \rceil) \le c f(n) 형태로 쓰면 ajf(nj)cjf(n)a^{j} f(n_{j}) \le c^{j} f(n)이 되어 exact powers proof와 같은 decreasing geometric series가 나온다. Case 2에서는 nj<n/bj+b/(b1)n_{j} < n/b^{j} + b/(b-1)를 이용해 f(nj)=O(nlogba/aj)f(n_{j})=O(n^{\log_{b} a}/a^{j})를 보이면 각 level cost가 O(nlogba)O(n^{\log_{b} a})로 유지된다. Case 1도 같은 구조지만 지수에 ε-\varepsilon가 들어가 algebra가 조금 더 복잡할 뿐이다.

핵심은 floor/ceiling이 recursion depth와 level cost를 상수배 이상으로 흔들지 않는다는 점이다. 그래서 master theorem은 exact powers뿐 아니라 모든 integer input size에 적용된다.

Proof가 알려주는 사용상의 감각

nlogban^{\log_{b} a}는 leaf가 얼마나 많아지는지를 나타낸다. 이것은 “recursive branching이 만들어내는 일의 양”이다. 반대로 f(n)f(n)은 각 node에서 recursive calls 바깥으로 수행하는 divide/combine cost다. master method는 이 둘의 성장률을 비교해 tree의 어느 부분이 총합을 지배하는지 판단한다.

판단 질문의미
f(n)f(n)nlogban^{\log_{b} a}보다 polynomially smaller인가?leaves가 지배하므로 Case 1
f(n)f(n)nlogban^{\log_{b} a}와 같은 차수인가?모든 level이 비슷하게 기여하므로 Case 2
f(n)f(n)이 polynomially larger이고 regularity condition을 만족하는가?root 쪽 cost가 지배하므로 Case 3
차이가 lgn\lg n 정도뿐인가?polynomial gap이 아니므로 기본 master theorem이 바로 적용되지 않을 수 있음

예를 들어 T(n)=4T(n/2)+n2lgnT(n)=4T(n/2)+n^{2} \lg n에서는 nlog24=n2n^{\log_{2} 4}=n^{2}이고 f(n)=n2lgnf(n)=n^{2} \lg n이다. f(n)f(n)n2n^{2}보다 크지만 polynomially larger가 아니라 logarithmic factor만 크다. 따라서 기본 master theorem의 Case 3으로 곧장 처리할 수 없다. 이런 경우에는 recursion tree, substitution method, 또는 확장된 master theorem이 필요하다.

Exercises, problems, chapter notes에서 남겨야 할 연결

Chapter 4의 exercises와 problems는 단순 계산 문제가 아니라 이 장의 method들이 어디까지 적용되는지 확인하게 한다.

Chapter notes의 실전적 메시지도 중요하다. Strassen’s algorithm은 asymptotically faster이지만 항상 practical winner는 아니다. hidden constant가 크고, sparse matrices에는 특화 알고리즘이 더 빠르며, numerical stability와 extra space 문제가 있다. 실제 dense matrix multiplication 구현은 큰 matrix에서는 Strassen을 쓰다가 subproblem이 crossover point 아래로 작아지면 straightforward method로 바꾸는 hybrid 방식을 쓴다. 이 crossover point는 cache, pipelining, machine architecture에 크게 의존한다.

마지막으로 master method는 간단하지만 equal subproblem size recurrence에 맞춰져 있다. T(n)=T(n/3)+T(2n/3)+O(n)T(n)=T(\lfloor n/3 \rfloor)+T(\lfloor 2n/3 \rfloor)+O(n) 같은 recurrence는 master method로 직접 풀 수 없고, 더 일반적인 Akra-Bazzi method가 필요하다. Akra-Bazzi는 여러 비율의 subproblems를 허용하며, aibip=1\sum a_{i} b_{i}^{p} = 1을 만족하는 p를 찾은 뒤 integral term으로 해를 표현한다.

복잡도

대상recurrence / bound핵심 이유
Maximum-subarray divide-and-conquerT(n)=2T(n/2)+Θ(n)T(n)=2T(n/2)+\Theta(n)Θ(nlgn)\Theta(n \lg n)left/right/crossing 세 후보, crossing 계산이 linear
Simple matrix multiplicationT(n)=8T(n/2)+Θ(n2)T(n)=8T(n/2)+\Theta(n^{2})Θ(n3)\Theta(n^{3})8 recursive multiplications plus matrix add
Strassen’s algorithmT(n)=7T(n/2)+Θ(n2)T(n)=7T(n/2)+\Theta(n^{2})Θ(nlg7)\Theta(n^{\lg 7})1 recursive multiplication을 14회 add/subtract와 교환
Substitution methodproof techniqueguess 후 induction으로 upper/lower/tight bound 증명
Recursion-tree methodguess generator + proof intuitionlevel cost와 leaf cost 분포를 보고 bound 추측
Master method Case 1f(n)=O(nlogbaε)f(n)=O(n^{\log_{b} a-\varepsilon})Θ(nlogba)\Theta(n^{\log_{b} a})leaves dominate
Master method Case 2f(n)=Θ(nlogba)f(n)=\Theta(n^{\log_{b} a})Θ(nlogbalgn)\Theta(n^{\log_{b} a} \lg n)all levels balanced
Master method Case 3f(n)=Ω(nlogba+ε)f(n)=\Omega(n^{\log_{b} a+\varepsilon}) + regularity → Θ(f(n))\Theta(f(n))root/top levels dominate

연결 관계

오해하기 쉬운 내용

면접 질문

  1. maximum-subarray problem에서 optimal subarray가 left, right, crossing 중 하나일 수밖에 없는 이유를 설명하라.
  2. FIND-MAX-CROSSING-SUBARRAY가 linear time인 이유와, 왜 middle을 반드시 포함해야 하는지 설명하라.
  3. Strassen’s algorithm이 Θ(n3)\Theta(n^{3})보다 빠른 이유를 recurrence 관점에서 설명하라.
  4. T(n)=3T(n/4)+cn2T(n)=3T(n/4)+cn^{2}를 recursion tree로 분석하면 왜 Θ(n2)\Theta(n^{2})가 되는가?
  5. Substitution method에서 lower-order term 때문에 induction이 실패할 때 어떻게 stronger hypothesis를 세우는가?
  6. Master theorem의 nlogban^{\log_{b} a}는 recursion tree에서 무엇을 의미하는가?
  7. Case 1, Case 2, Case 3을 각각 “leaves dominate”, “levels balanced”, “root dominates”로 설명하라.
  8. T(n)=2T(n/2)+nlgnT(n)=2T(n/2)+n \lg n에 기본 master theorem을 바로 적용하기 어려운 이유는 무엇인가?
  9. Floor/ceiling이 recurrence solution의 asymptotic bound를 보통 바꾸지 않는 이유를 recursion depth 관점에서 설명하라.
  10. Strassen’s algorithm이 asymptotically faster인데도 실제 구현에서 항상 선택되지 않는 이유를 말하라.

Share this post on:

Previous Post
Chapter 6. Heapsort
Next Post
Chapter 3. Growth of Functions