: 그대로 해결할 수 없는 문제를 작은 문제로 분할하여 문제를 해결하는 방법
Quick Sort나 Merge Sort로 대표되는 정렬 알고리즘 문제가 대표적이다.
분할 정복 알고리즘은 보통 재귀 함수(recursive function)를 통해 자연스럽게 구현된다. 예를 들면,
function F(x):
if F(x)의 문제가 간단 then:
return F(x)을 직접 계산한 값
else:
x를 y1, y2로 분할
F(y1)과 F(y2)를 호출
return F(y1), F(y2)로부터 F(x)를 구한 값
또한 여기서 작은 부문제로 분할할 경우에 부문제를 푸는 알고리즘은 임의로 선택할 수 있다. 이러한 재귀호출을 사용한 함수는 함수 호출 오버헤드 때문에 실행 속도가 늦어진다.
빠른 실행이나 부문제 해결 순서 선택을 위해, 재귀호출을 사용하지 않고 스택, 큐(queue) 등의 자료구조를 이용하여 분할 정복법을 구현하는 것도 가능하다.
: 문제를 나눔으로써 어려운 문제를 해결할 수 있다는 엄청나게 중요한 장점이 있다. 그리고 이 방식이 그대로 사용되는 효율적인 알고리즘들도 여럿 있으며, 문제를 나누어 해결한다는 특징상 병력적으로 문제를 해결하는 데 큰 강점이 있다.
: 함수를 재귀적으로 호출한다는 점에서 함수 호출로 인한 오버헤드가 발생하며, 스택에 다양한 데이터를 보관하고 있어야 하므로 스택 오버플로우가 발생하거나 과도한 메모리 사용을 하게 되는 단점
int consecutive_sum(start, end){
if(start == end) return start;
mid = (start + end) / 2;
return consecutive_sum(start, mid) + consecutive_sum(mid + 1, end);
}
# 출력
System.out.println(consecutive_sum(1, 100))
# 결과 값
5050
1) Divide: 1 ~ n까지의 합을 절반 씩 나눈다.
2) Conquer: 절반씩 나눈 것들의 합을 구한다.
3) Combine: Conquer에서 구한 값들을 모두 합친다.
k - 1단계에서는 2^(k - 1)번, 2단계에서는 2^2=4번, 1단계에서는 2번, 0단계에서는 1번의 병합을 해야 하는데 각 병합에 걸리는 시간이 해당 문제 크기가 N일때 O(N)이다.
그러므로 각 단계별로 드는 연산 횟수를 죽 늘어놓았을 때
0단계: 1 O(N)
1단계: 2 O(N / 2)
2단계: 4 O(N / 4)
m단계: 2^m O(N / (2^m)) = O(N)
따라서 단계별 식을 일반화해 보면 각 단계에서 드는 총합 연산량은 O(N)이다. 이는 매 단계마다 각 문제의 크기 역시 지수적으로 줄어들기 때문이다.
각 단계는 총 O(logN)개 있으므로, 이를 곱해서 O(NlogN)이라는 시간복잡도를 구할 수 있다.
=> 버블 정렬, 선택 정렬 등의 기본적인 알고리즘은 O(N^2)의 시간복잡도를 가지고 있다.