순환적으로 문제를 푸는 하향식 (Top - Down)설계 방법
- 분할된 작은 문제들의 각 해를 구하고, 해를 결합하여 원래의 문제의 해를 구함
주어진 문제를 여러개의 소문제로 분할
소문제들을 순환적으로 분할.
- 더 이상 분할되지 않을 정도로 충분히 작다면, 순환 호출 없이 소문제의 해를 구함
소문제에 대해 정복된 해를 결합하여 원래 문제의 해를 구함
- 일부 알고리즘의 경우, 결합 과정이 없는 케이스도 존재
정렬된 상태의 데이터에 대한 효과적 탐색 방법
- 오름차순으로 정렬되어 있다고 가정
// Pseudo code
BinarySearch(A[], Left, Right, x){
if (Left > Right) return -1; // 탐색 실패
Mid = (Left + Right) / 2;
if (x == A[Mid]) return Mid;
else if (x < Mid)
BinarySearch(A, Left, Mid-1, x); // 왼쪽 부분배열
else
BinarySearch(A, Mid+1, Right, x); // 오른쪽 부분배열
}
- T(n) = 입력크기 n에 대한 모든 비교 횟수의 합 = 맨 바깥 수준에서의 비교 횟수 + 순환 호출에서의 비교 횟수
특정 원소를 기준으로 기준 배열을 두 부분배열로 분할하고,
각 부분배열에 대해서 퀵 정렬을 순환적으로 적용
분할원소가 제자리를 잡도록 하여 정렬하는 방식
// Pseudo code
QuickSort(A[], n){
if (n > 1) {
// 분할 원소를 기준으로 두 부분배열로 분할
pivot = Patrition(A[0...n-1], n);
// 왼쪽 부분배열에 대한 순환호출
QuickSort(A[0...pivot-1], pivot);
// 오른쪽 부분배열에 대한 순환호출
QuickSort(A[pivot+1...n], n-pivot-1);
}
}
int Partition(A[], n){
Left = 1; Right = n-1;
while (Left < Right) { // 분할 원소 A[0]
// 분할원소보다 큰 값의 위치를 찾음
while (Left < n && A[Left] < A[0]) Left++;
// 분할원소보다 작은 값의 위치를 찾음
while (Right > 0 && A[Right] >= A[0]) Right++;
// 분할원소 이동
if (Left < Right) 교환(A[Left] <-> A[Right]
else 교환(A[0] <-> A[Right)
}
return Right;
}
Partition 함수
최악 수행 시간
배열을 동일한 크기의 두 부분 배열로 분할하고,
각각의 부분 배열을 순환적으로 정렬한 후,
정렬된 두 부분배열을 합병하여 하나의 정렬된 배열을 만드는 방식
# Pseucode
MergeSort(A[], n) {
if (n > 1) {
Mid = n/2;
B[0...Mid-1] = MergeSort(A[0...Mid-1], Mid); // 왼쪽
C[0...n-Mid-1] = MergeSort(A[Mid....n-1], n-Mid); // 오른쪽
// 합병
A[0...n-1] = Merge(B[0...Mid-1), C[0...n-Mid-1], Mid, n-Mid);
}
}
Merge(B[], C[], n, m){
i = j = k = 0;
while (i < n && j < m) {
if (B[i] <= C[j])
// B[i]를 선택 후 i를 1증가
A[k++] = B[i++];
else
// C[j]를 선택 후 j를 1증가
A[k++] = C[j++]
}
for (; i<n; i++) A[k++] = B[i]; // 남은 원소 이동
for (; i<n; i++) A[k++] = C[j]; // 남은 원소 이동
return A[0....n+m-1];
}
Merge() 함수
합병 정렬 수행 시간
n개의 원소가 임의의 순서로 저장된 배열에서 i번째로 작은 원소를 찾은 문제
- i=1 -> 최소값,
- i = n/2 -> 중간값,
- i=n -> 최대값
퀵정렬의 Partition() 함수를 순환적용
// Pseudo code
int Selection(A[], n, i){
Left = 0; Right = n-1;
j = Partition(A, n);
if (i == j+1) return A[j]; // i번째 최소값
else if (i < j + 1)
Selection(A[Left...j-1], (j-1)-Left+1, i); // 왼쪽 부분 배열
else
Selection(A[j+1...Right], Right-(j+1)+1, i-(j+1); // 오른쪽 부분 배열
}
항상 일정한 비율의 두 부분배열로 분할
- 최악 O(n)
- 아래에 설명할 거임
특정한 성질을 만족하는 원소를 분할원소로 선택
- 항상 일정한 비율의 두 부분배열로 분할
// Pseudo code
int Selection_n(A[], n, i) {
[단계 1] if (n < 5) 배열 A에서 i번째 원소를 찾아서 반환
else [단계 2] ~ [단계 6] 진행
[단계 2] A의 원소를 5개씩 묶어서 n/5개의 그룹 생성
[단계 3] 각 그룹의 중간 값을 구하고, 이들을 모다 배열 M을 구성
[단계 4] 중간값들의 중간값을 계산하기 위해 선택함수를 순환 적용 // 분할 원소의 인덱스 -> j
p = Selection_n(M, n/5, n/5/2)
[단계 5] p를 분할 원소로 사용하여 A를 분할
[단계 6] if ( i == j + 1) return A[j]
else if (i < j + 1) Selection_n(A[0...j-1], j, i) // 순환 호출
else Selection_n(A[j+1...n-1], n-j-1, i-j-1) // 순환 호출
}
2차원 평면상에 있는 n개의 점들 중에서
서로의 거리가 가장 가까운 두 점을 찾는 문제
- O(n^2) -> 분할 정복을 적용하면 O(nlogn)
// Pseudo code
int CPP(S[], n){
// 입력 S[0...n-1] -> 입력 점집합
// n -> 집합 S의 원소 개수
// 출력 -> 최근접 점쌍 간의 거리
[단계 0] 점집합 S를 x좌표의 오름차순으로 정렬하여 S_x를 구하고,
y좌표의 오름차순으로 정렬한 S_y를 구 // O(nlogn)
[단계 1] S를 이등분하는 수직선 l를 긋고,
이것의 좌측의 점 S_l과 우측의 점집합 S_r을 구함 // O(n)
[단계 2] S_l의 최근접 점쌍을 순환적으로 구한다.
구한 거리를 d_l이라고 함. // T(n/2)
[단계 3] S_r의 최근접 점쌍을 순환적으로 구한다.
구한 거리를 d_r이라고 함. // T(n/2)
[단계 4] 결합
d = mid{d_l, d_r}
// S_y`을 S_y에서 수직선 l을 중심으로 +-d 이내에 있는 점만으로 정의
// O(n)
for ( i in S_y`)
for (j = i+1; j<=i+7; j++)
if (점 i와 점 j의 y좌표 차이가 d 이상) break;
else if (점 i와 점 j의 거리가 d보다 작으면)
d를 점 i와 점 j의 거리로 바꿈;
return d;
}