본문으로 건너뛰기
김신건의 로그

분할 정복 (Divide and Conquer)

· 수정 · 📖 약 3분 · 1,076자/단어 #algorithm #divide-conquer #recursion
divide and conquer, 분할 정복, divide-and-conquer

정의

분할 정복 (Divide and Conquer) 은 문제를 더 작은 부분 문제로 나누고 (Divide), 각각 해결한 후 (Conquer), 결과를 합쳐 (Combine) 원래 문제의 답을 얻는 알고리즘 설계 패러다임.

핵심 3 단계:

  1. Divide: 문제를 k 개 (보통 2 개) 부분 문제로 분할
  2. Conquer: 재귀적으로 각 부분 문제 해결 (base case 에서 직접 풀기)
  3. Combine: 부분 해를 합쳐 전체 해 구성

대표 알고리즘: 병합 정렬 (Merge Sort), 퀵 정렬 (Quick Sort), 이진 탐색 (Binary Search), 고속 푸리에 변환 (FFT), 카라츠바 곱셈.

문제 상황과 동기

정렬 문제 예시:

  • naive (버블 정렬): O(N^2), N = 10^6 이면 10^12 연산, 불가능.
  • 분할 정복 (병합 정렬): O(N log N), N = 10^6 이면 ~2 × 10^7, 0.1 초.

핵심 통찰: 큰 문제를 반으로 나누면 T(N) = 2T(N/2) + O(N) → O(N log N). 재귀 깊이 log N, 각 레벨 O(N) 작업 → 총 O(N log N).

자주 등장하는 컨텍스트:

  • PS: 정렬, 탐색, 수학 (거듭제곱, 행렬 곱), 기하 (가장 가까운 점 쌍)
  • 실무: 정렬 알고리즘 (Merge Sort, Quick Sort), MapReduce, 병렬 처리

시각화

핵심 아이디어

재귀 관계식 (Master Theorem):

T(N) = a × T(N/b) + f(N)

a = 부분 문제 개수
b = 분할 크기 (N/b)
f(N) = 분할 + 합치기 비용

Master Theorem 결론:

  1. f(N) = O(N^c), c < log_b(a) → T(N) = Θ(N^(log_b(a)))
  2. f(N) = Θ(N^c log^k N), c = log_b(a) → T(N) = Θ(N^c log^(k+1) N)
  3. f(N) = Ω(N^c), c > log_b(a) → T(N) = Θ(f(N))

병합 정렬 예시:

T(N) = 2 × T(N/2) + O(N)
a = 2, b = 2, f(N) = N
log_2(2) = 1, c = 1 → case 2
T(N) = O(N log N)

퀵 정렬 예시 (평균):

T(N) = 2 × T(N/2) + O(N)  (pivot 가 중간값이면)
→ O(N log N) 평균
최악 O(N^2) (pivot 가 최소/최대)

알고리즘

병합 정렬 (Merge Sort):

merge_sort(arr, left, right):
    if left >= right: return

    mid = (left + right) / 2
    merge_sort(arr, left, mid)      // Divide
    merge_sort(arr, mid + 1, right) // Divide
    merge(arr, left, mid, right)    // Combine

merge(arr, left, mid, right):
    L = arr[left..mid]
    R = arr[mid+1..right]
    i = 0, j = 0, k = left
    while i < len(L) and j < len(R):
        if L[i] <= R[j]:
            arr[k] = L[i]; i++
        else:
            arr[k] = R[j]; j++
        k++
    while i < len(L): arr[k++] = L[i++]
    while j < len(R): arr[k++] = R[j++]

구현

// 병합 정렬 (Merge Sort), O(N log N)
#include <bits/stdc++.h>
using namespace std;

void merge(vector<int>& arr, int left, int mid, int right) {
  vector<int> L(arr.begin() + left, arr.begin() + mid + 1);
  vector<int> R(arr.begin() + mid + 1, arr.begin() + right + 1);
  int i = 0, j = 0, k = left;
  while (i < (int)L.size() && j < (int)R.size()) {
      if (L[i] <= R[j]) arr[k++] = L[i++];
      else arr[k++] = R[j++];
  }
  while (i < (int)L.size()) arr[k++] = L[i++];
  while (j < (int)R.size()) arr[k++] = R[j++];
}

void merge_sort(vector<int>& arr, int left, int right) {
  if (left >= right) return;
  int mid = (left + right) / 2;
  merge_sort(arr, left, mid);
  merge_sort(arr, mid + 1, right);
  merge(arr, left, mid, right);
}

int main() {
  int n; cin >> n;
  vector<int> arr(n);
  for (auto& v : arr) cin >> v;
  merge_sort(arr, 0, n - 1);
  for (int v : arr) cout << v << " ";
  cout << "\n";
}
stdin
5
5 3 8 1 2
결과
1 2 3 5 8

복잡도

항목Merge SortQuick Sort (평균)Binary Search
시간 (최선)O(N log N)O(N log N)O(1)
시간 (평균)O(N log N)O(N log N)O(log N)
시간 (최악)O(N log N)O(N^2)O(log N)
공간O(N)O(log N)O(1)
안정성N/A

변형 / 활용

1. 퀵 정렬 (Quick Sort)

pivot 을 기준으로 분할. 평균 O(N log N), 최악 O(N^2). 실무에서 정렬 라이브러리 (introsort 등) 의 기본.

정렬된 배열에서 O(log N) 탐색. 분할만 있고 합치기 없음.

3. 카라츠바 곱셈 (Karatsuba Multiplication)

큰 정수 곱셈을 O(N^1.585) 로. 분할해서 3 번만 재귀 곱셈.

4. 고속 푸리에 변환 (FFT)

다항식 곱셈을 O(N log N) 로. 분할 정복 + 복소수 회전.

5. 행렬 거듭제곱

A^N 을 O(log N) 번 곱셈으로.

mat pow(mat A, int N) {
    if (N == 1) return A;
    mat half = pow(A, N / 2);
    mat res = half * half;
    if (N % 2) res = res * A;
    return res;
}

함정

1. Base case 미정의

재귀 종료 조건이 없으면 스택 오버플로우. if (left >= right) return; 필수.

2. 중복 계산

분할 경계가 겹치면 중복 재귀. DP 로 전환 필요.

3. Combine 비용 간과

합치기가 O(N^2) 면 총 복잡도 O(N^2 log N). 병합 정렬은 O(N) 합치기 → O(N log N).

4. 메모리 오버헤드

병합 정렬은 O(N) 추가 공간. 퀵 정렬은 in-place 가능 (O(log N) 스택).

BOJ 연습 문제

번호제목정답률링크
BOJ 2104부분 배열 고르기-kokoa-lab
BOJ 1780종이의 개수-kokoa-lab
BOJ 1992쿼드트리-kokoa-lab
BOJ 2261가장 가까운 두 점-kokoa-lab
BOJ 11729하노이 탑 이동 순서-kokoa-lab

참고

이 글의 용어 (5개)
동적 계획법 (Dynamic Programming)algorithm
정의 동적 계획법 (Dynamic Programming, DP) 은 큰 문제를 작은 부분 문제로 나누고, 각 부분 문제의 최적해를 저장하여 중복 계산을 제거하는 최적화 기법. R…
이분 탐색 (Binary Search)algorithm
정의 이분 탐색 (Binary Search) 은 정렬된 시퀀스에서 목표값의 위치를 O(log N) 에 찾는 알고리즘. 매 단계에서 후보 구간을 절반으로 줄인다. 탐색이 본질이 아…
FFT, NTTalgorithm
정의 FFT (Fast Fourier Transform, 고속 푸리에 변환) 은 길이 인 수열의 이산 푸리에 변환 (DFT) 을 O(n log n) 에 계산하는 분할정복 알고리즘…
Merge Sortalgorithm
정의 Merge Sort (병합 정렬) 는 분할 정복 (Divide & Conquer) 으로 동작하는 비교 정렬. 배열을 반으로 나눠 각각 정렬한 뒤, 두 정렬된 부분을 병합 (…
Quick Sortalgorithm
정의 Quick Sort (퀵 정렬) 는 분할 정복 (Divide & Conquer) 기반 비교 정렬. 1959 년 Tony Hoare 가 고안. 배열에서 pivot 을 하나 골…

💬 댓글

사이트 검색 / 명령어

검색

스크롤 = 확대/축소 · 드래그 = 이동 · 0 = 원래 크기 · ESC = 닫기