Skip to Content
독학사독학사 4단계알고리즘14. 분할정복 설계 기법과 대표 문제

이번 문서의 목표: 이 문서를 다 읽으면 분할정복 알고리즘의 분할·정복·결합 3단계를 구체적인 예제로 설명할 수 있고, 분할정복 알고리즘의 점화식을 세운 뒤 마스터 정리로 시간 복잡도를 구할 수 있으며, 분할정복이 유리한 경우와 그렇지 않은 경우를 구분할 수 있다.

왜 분할정복이 따로 필요한가

07편에서 다룬 퀵 정렬·병합 정렬은 사실 “큰 문제를 작은 문제로 쪼개 각각 풀고 합친다”는 하나의 공통된 설계 원리를 공유한다. 이 원리를 정렬에만 한정하지 않고 일반화하면, 정렬 말고도 거듭제곱 계산, 최근접 점 쌍 찾기, 행렬 곱셈처럼 전혀 다른 문제에도 똑같이 적용할 수 있다. 분할정복(divide and conquer)은 이렇게 “문제를 작게 나누고, 작은 문제를 풀고, 결과를 합친다”는 패턴 자체를 하나의 설계 기법으로 다루는 관점이다.

쉽게 말하면: 분할정복은 큰 문제를 그대로 풀기 어려우니, 작은 조각으로 쪼개 각 조각을 풀고 그 답들을 이어 붙여 원래 문제의 답을 만드는 전략이다.

분할정복의 세 단계

분할정복 알고리즘은 항상 다음 세 단계로 구성된다.

  1. 분할(divide): 원래 문제를 같은 종류의 더 작은 부분 문제 여러 개로 나눈다.
  2. 정복(conquer): 각 부분 문제를 재귀적으로 푼다. 부분 문제가 충분히 작아지면(기저 사례, base case) 더 나누지 않고 직접 답을 구한다.
  3. 결합(combine): 부분 문제들의 답을 모아 원래 문제의 답을 만든다.

07편의 병합 정렬로 이 세 단계를 다시 확인하면, 분할은 “배열을 절반으로 나누는 것”, 정복은 “각 절반을 재귀적으로 정렬하는 것”, 결합은 “정렬된 두 절반을 하나로 병합하는 것”이다. 반면 퀵 정렬은 분할 단계(피벗 기준으로 나누기)에서 대부분의 일을 끝내고 결합 단계는 사실상 아무 일도 하지 않는다는 점이 병합 정렬과 대조적이다.

대표 예제 1 — 빠른 거듭제곱 계산

거듭제곱 ana^n을 계산하는 가장 단순한 방법은 aann번 곱하는 것으로, O(n)O(n)번의 곱셈이 필요하다. 분할정복을 적용하면 이를 O(logn)O(\log n)으로 줄일 수 있다.

핵심 아이디어는 ana^n을 절반 크기의 거듭제곱으로 표현하는 것이다.

an={1n=0일 때(an/2)2n이 짝수일 때a×(a(n1)/2)2n이 홀수일 때a^n = \begin{cases} 1 & n = 0 \text{일 때} \\ \left(a^{n/2}\right)^2 & n \text{이 짝수일 때} \\ a \times \left(a^{(n-1)/2}\right)^2 & n \text{이 홀수일 때} \end{cases}
  • an/2a^{n/2}: 지수를 절반으로 줄인 부분 문제. 이 값을 한 번만 계산하고 제곱해서 재사용하는 것이 핵심이다(만약 an/2a^{n/2}을 두 번 따로 계산하면 나이브 방식과 다를 게 없어진다).
  • nn이 홀수일 때는 짝을 맞추기 위해 aa 하나를 따로 곱해 준다.
Power(a, n): 만약 n == 0이면 1을 반환한다 (기저 사례) 절반값 = Power(a, n을 2로 나눈 몫) (분할 + 정복: 절반 크기 부분 문제를 재귀로 품) 만약 n이 짝수이면 절반값 × 절반값을 반환한다 (결합) 그렇지 않으면(n이 홀수) a × 절반값 × 절반값을 반환한다 (결합)

작은 예시로 추적하기

3133^{13}을 계산하는 과정을 재귀 호출 트리로 추적한다.

호출n의 홀짝재귀 결과 이용계산반환값
Power(3, 0)- (기저 사례)--1
Power(3, 1)홀수Power(3, 0) = 13×123 \times 1^23
Power(3, 3)홀수Power(3, 1) = 33×323 \times 3^227
Power(3, 6)짝수Power(3, 3) = 2727227^2729
Power(3, 13)홀수Power(3, 6) = 7293×72923 \times 729^21,594,323

호출 순서를 보면 13631013 \to 6 \to 3 \to 1 \to 0으로, 매 호출마다 nn이 대략 절반으로 줄어든다. 실제로 313=1,594,3233^{13} = 1{,}594{,}323이 맞는지는 313=38×34×313^{13} = 3^8 \times 3^4 \times 3^1로 검산할 수 있다. 나이브 방식이라면 곱셈을 12번 해야 했을 자리를, 분할정복은 재귀 호출 4번(그리고 각 단계에서 곱셈 1~2번)만으로 끝낸다.

쉽게 말하면: 지수를 계속 반으로 접어 나가면서, “절반을 한 번만 계산하고 제곱해서 재사용”하는 것이 곱셈 횟수를 크게 줄이는 비결이다.

대표 문제 2 — 최근접 점 쌍 문제(개요)

평면 위에 점이 nn개 있을 때 서로 가장 가까운 두 점을 찾는 최근접 점 쌍(closest pair of points) 문제도 분할정복으로 풀 수 있는 대표적인 문제다. 모든 점 쌍의 거리를 다 재는 나이브 방식은 O(n2)O(n^2)이 걸리지만, 분할정복은 다음과 같은 흐름으로 이를 줄인다.

  • 분할: 점들을 x좌표 기준으로 정렬한 뒤, 중간을 기준으로 왼쪽 절반과 오른쪽 절반으로 나눈다.
  • 정복: 각 절반에서 재귀적으로 최근접 점 쌍을 구한다.
  • 결합: 왼쪽과 오른쪽 최솟값 중 더 작은 값을 dd라 하고, 경계선 근처 폭 2d2d 안에 있는 점들만 따로 모아 그 안에서만 추가로 거리를 비교한다(경계 안의 점들만 보면 되므로 이 결합 단계가 O(n)O(n)에 끝난다는 것이 이 알고리즘의 정교한 부분이다).

이 문제는 독학사 시험에서 알고리즘을 직접 손코딩하기보다는 “분할정복으로 O(nlogn)O(n \log n)에 풀 수 있는 대표적인 기하 문제”라는 존재와 흐름 정도를 알아 두면 충분하다.

점화식 세우기와 마스터 정리

분할정복 알고리즘의 시간 복잡도는 대부분 다음과 같은 형태의 점화식으로 나타난다(04편에서 다룬 점화식 기초를 분할정복에 적용한다).

T(n)=a×T(n/b)+O(nd)T(n) = a \times T(n/b) + O(n^d)
  • aa: 한 번의 호출에서 만들어지는 부분 문제의 개수.
  • n/bn/b: 부분 문제 하나의 크기(원래 크기를 bb로 나눈 것).
  • O(nd)O(n^d): 분할과 결합에 드는 비용(재귀 호출 자체를 제외한 비용).

마스터 정리(master theorem)는 aa, bb, dd 세 값만으로 T(n)T(n)의 점근적 표기를 바로 구할 수 있게 해 준다. logba\log_b a(밑이 bbaa의 로그, “부분 문제 개수가 부분 문제 크기의 몇 제곱에 해당하는 속도로 늘어나는가”를 나타내는 지수)와 dd를 비교한다.

T(n)={O(nd)logba<d일 때 (분할⋅결합 비용이 지배적)O(ndlogn)logba=d일 때 (둘의 크기가 같은 차수)O(nlogba)logba>d일 때 (재귀 호출 자체가 지배적)T(n) = \begin{cases} O(n^d) & \log_b a < d \text{일 때 (분할·결합 비용이 지배적)} \\ O(n^d \log n) & \log_b a = d \text{일 때 (둘의 크기가 같은 차수)} \\ O(n^{\log_b a}) & \log_b a > d \text{일 때 (재귀 호출 자체가 지배적)} \end{cases}

적용 예 1 — 병합 정렬

병합 정렬(07편)의 점화식은 T(n)=2T(n/2)+O(n)T(n) = 2T(n/2) + O(n)이다. 여기서 a=2a=2, b=2b=2, d=1d=1이다.

logba=log22=1\log_b a = \log_2 2 = 1

logba=1\log_b a = 1이고 d=1d = 1이므로 두 값이 같은 경우(두 번째 경우)에 해당해, T(n)=O(n1logn)=O(nlogn)T(n) = O(n^1 \log n) = O(n \log n)이다. 이는 07편에서 재귀 트리로 직접 유도했던 결과와 정확히 일치한다.

적용 예 2 — 빠른 거듭제곱

빠른 거듭제곱의 점화식은 T(n)=T(n/2)+O(1)T(n) = T(n/2) + O(1)이다. 여기서 a=1a=1, b=2b=2, d=0d=0(분할·결합에 드는 비용이 지수·곱셈 한 번이라는 상수 시간이므로 n0n^0).

logba=log21=0\log_b a = \log_2 1 = 0

logba=0\log_b a = 0이고 d=0d = 0이므로 역시 두 번째 경우에 해당해, T(n)=O(n0logn)=O(logn)T(n) = O(n^0 \log n) = O(\log n)이다. 앞서 추적표에서 호출 4번(13631013\to6\to3\to1\to0)만으로 끝난 것이 바로 이 O(logn)O(\log n)의 실제 모습이다.

자주 틀리는 점: 마스터 정리를 적용할 때 dd를 “재귀 호출 자체를 포함한 전체 비용”으로 잘못 넣는 실수가 잦다. dd분할과 결합에만 드는 비용(재귀 호출 부분을 뺀 나머지)이어야 한다. 병합 정렬에서 d=1d=1인 것은 “두 배열을 병합하는 데 드는 O(n)O(n)“만을 가리키며, 재귀로 절반씩 정렬하는 비용은 a×T(n/b)a \times T(n/b) 항이 이미 따로 표현하고 있다.

분할정복의 장단점과 시험 포인트

항목내용
장점부분 문제가 서로 독립적이면 병렬 처리가 자연스럽고, 문제를 작게 쪼개면 사람이 이해·증명하기도 쉬워진다. 대부분 O(nlogn)O(n \log n) 수준의 효율적인 알고리즘으로 이어진다.
단점부분 문제를 나누고 결과를 합치는 과정 자체에 비용(예: 병합 정렬의 병합 단계)이 들며, 재귀 호출로 인한 함수 호출 오버헤드·스택 공간 소모가 있다. 부분 문제들이 겹치는 경우(16편의 동적계획법이 다루는 상황)에는 같은 부분 문제를 반복해서 풀게 되어 비효율적일 수 있다.
시험 포인트”이 알고리즘의 점화식을 세우고 마스터 정리로 시간 복잡도를 구하라”는 유형이 자주 나온다. aa, bb, dd를 정확히 뽑아내는 것이 관건이며, 분할정복과 동적계획법(16편)을 구분하는 기준(부분 문제가 겹치는지 여부)도 함께 자주 출제된다.

자주 틀리는 점: “분할정복은 항상 효율적이다”라고 단정하면 안 된다. 부분 문제들이 서로 겹쳐서(예: 피보나치 수를 단순 재귀로 계산하면 같은 부분 문제를 지수적으로 반복 계산하게 됨) 오히려 비효율적인 경우도 있으며, 이런 경우는 16편의 동적계획법으로 부분 문제의 답을 저장해 재사용하는 편이 낫다.

핵심 정리

  • 분할정복은 분할(작은 부분 문제로 나누기), 정복(부분 문제를 재귀로 풀기), 결합(부분 문제의 답을 합치기)의 세 단계로 이루어진다.
  • 빠른 거듭제곱 ana^n은 절반 크기의 거듭제곱을 한 번만 계산해 제곱함으로써 O(logn)O(\log n)에 계산할 수 있다.
  • 분할정복 알고리즘의 점화식 T(n)=aT(n/b)+O(nd)T(n) = aT(n/b) + O(n^d)은 마스터 정리로 logba\log_b add를 비교해 O(nd)O(n^d), O(ndlogn)O(n^d \log n), O(nlogba)O(n^{\log_b a}) 중 하나로 정리된다.
  • 부분 문제가 서로 독립적일 때는 분할정복이 유리하지만, 부분 문제가 겹칠 때는 동적계획법이 더 효율적일 수 있다.

마무리 복습

문제 14지선다
분할정복 알고리즘을 구성하는 세 단계로 옳은 것은?
문제 24지선다
빠른 거듭제곱 계산에서 지수 n이 짝수일 때 a^n을 (a^(n/2))^2로 계산하는 것이 나이브 방식(a를 n번 곱하기)보다 빠른 이유로 가장 적절한 것은?
문제 34지선다
분할정복 알고리즘의 점화식이 T(n) = 4T(n/2) + O(n)일 때, 마스터 정리를 적용하기 위한 log_b(a) 값과 d 값으로 옳은 것은?
문제 44지선다
병합 정렬의 점화식 T(n) = 2T(n/2) + O(n)에 마스터 정리를 적용했을 때의 결과로 옳은 것은?
문제 54지선다
분할정복 기법의 한계 또는 주의점으로 옳지 않은 것은?
문제 64지선다
퀵 정렬과 병합 정렬을 분할정복의 세 단계 관점에서 비교한 설명으로 가장 적절한 것은?

참고 자료

Last updated on