크기가 n인 입력을 3개로 분할하고, 각각 분할된 부분 문제의 크기가 n/2일 경우의 분할 예
MergeSort(A,p,q)
입력: A[p]~A[q]
출력: 정렬된 A[p]~A[q]
1. if ( p < q ) { // 배열의 원소의 수가 2개 이상이면
2. k = ⌊(p+q)/2⌋ // k는 중간 원소의 인덱스
3. MergeSort(A,p,k) // 앞부분 순환 호출
4. MergeSort(A,k+1,q) // 뒷부분 순환 호출
5. A[p]~A[k]와 A[k+1]~A[q]를 합병
}2개의 정렬된 배열 A와 B의 크기가 각각 m과 n이라면, 최대 비교 횟수
= (m+n-1)
합병의 시간 복잡도 = O(m+n)
각 층을 살펴보면 모든 숫자(즉, n=8개의 숫자)가 합병에 참여
합병은 입력 크기에 비례하므로 각 층에서 수행된 비교 횟수는 O(n)
층수를 세어보면, 8개의 숫자를 반으로, 반의 반으로 반의 반의 반으로 나눈다.
이 과정을 통하여 세 층이 만들어진다.
#include <stdio.h>
#include <stdlib.h>
void merge(int A[], int p, int k, int q) {
int n = q - p + 1;
int* tmp = (int*)malloc(sizeof(int) * n);
int i = p, j = k + 1, t = 0;
while (i <= k && j <= q) {
if (A[i] <= A[j]) tmp[t++] = A[i++];
else tmp[t++] = A[j++];
}
while (i <= k) tmp[t++] = A[i++];
while (j <= q) tmp[t++] = A[j++];
for (int m = 0; m < n; ++m) A[p + m] = tmp[m];
free(tmp);
}
void MergeSort(int A[], int p, int q) {
if (p < q) {
int k = (p + q) / 2;
MergeSort(A, p, k);
MergeSort(A, k + 1, q);
merge(A, p, k, q);
}
}
int main(void) {
int A[] = { 37, 10, 22, 30, 35, 13, 25, 24 };
int n = sizeof(A) / sizeof(A[0]);
MergeSort(A, 0, n - 1);
for (int i = 0; i < n; ++i) printf("%d ", A[i]);
printf("\n");
return 0;
}
퀵 정렬은 피봇 (pivot) 이라 일컫는 배열의 원소(숫자)를 기준으로 피봇보다 작은 숫자들은 왼편으로, 피봇보다 큰 숫자들은 오른편에 위치하도록 분할하고, 피봇을 그 사이에 놓는다.
퀵 정렬은 분할된 부분문제들에 대해서도 위와 동일한 과정을 순환으로 수행하여 정렬
피봇이 60이라면, 60은 [20 40 10 30 50]과 [70 90 80] 사이에 위치한다.
QuickSort(A, left, right)
입력: 배열 A[left]~A[right]
출력: 정렬된 배열 A[left]~A[right]
1. if (left < right) {
2. 피봇을 A[left]~A[right]에서 선택하고,
피봇을 A[left]와 자리를 바꾼 후, 피봇과 배열의 각
원소를 비교하여 피봇보다 작은 숫자들은
A[left]~A[p-1]로 옮기고, 피봇보다 큰 숫자들은
A[p+1]~A[right]로 옮기며, 피봇은 A[p]에 놓는다.
3. QuickSort(A, left, p-1) // 피봇보다 작은 그룹
4. QuickSort(A, p+1 right) // 피봇보다 큰 그룹
}
Line 2: 피봇을 제자리로 이동
최선 경우의 분할
각 층에서는 각각의 원소가 각 부분의 피봇과 1회씩 비교된다. 따라서 비교 횟수 = O(n)
가장 왼쪽 숫자, 중간 숫자, 가장 오른쪽 숫자 중에서 중앙값으로 피봇을 정한다.
아래의 예제를 보면, [31, 1, 26] 중에서 중앙값인 26을 피봇으로 사용
#include <stdio.h>
void swap(int* a, int* b) {
int t = *a; *a = *b; *b = t;
}
/* 피봇을 A[left]로 두고 Hoare 방식으로 분할 */
int partition(int A[], int left, int right) {
int pivot = A[left];
int i = left + 1, j = right;
while (1) {
while (i <= right && A[i] <= pivot) i++;
while (j >= left + 1 && A[j] > pivot) j--;
if (i > j) break;
swap(&A[i], &A[j]);
}
swap(&A[left], &A[j]); // 피봇을 최종 위치로
return j; // 피봇 인덱스 p
}
void QuickSort(int A[], int left, int right) {
if (left < right) {
int p = partition(A, left, right);
QuickSort(A, left, p - 1); // 피봇보다 작은 그룹
QuickSort(A, p + 1, right); // 피봇보다 큰 그룹
}
}
int main(void) {
int A[] = { 1, 17, 42, 9, 18, 23, 31, 11, 26 };
int n = sizeof(A) / sizeof(A[0]);
QuickSort(A, 0, n - 1);
for (int i = 0; i < n; ++i) printf("%d ", A[i]);
printf("\n");
return 0;
}
입력이 정렬되어 있지 않으므로, 입력 숫자들 중에서 (퀵 정렬과 같이) 피봇을 선택하여 분할
Selection(A, left, right, k)
입력: A[left]~A[right]와 k, 단, 1≤k≤|A|, |A|=right-left+1
출력: A[left]~A[right]에서 k 번째 작은 원소
1. 피봇을 A[left]~A[right]에서 랜덤하게 선택하고, 피봇과 A[left]의
자리를 바꾼 후, 피봇과 배열의 각 원소를 비교하여 피봇보다 작은 숫자
는 A[left]~A[p-1]로 옮기고, 피봇보다 큰 숫자는 A[p+1]~ A[right]
로 옮기며, 피봇은 A[p]에 놓는다.
2. S = (p-1)-left+1 // S = Small group의 크기
3. if ( k ≤ S ) Selection(A, left, p-1, k) // Small group에서 찾기
4. else if ( k = S + 1 ) return A[p] // 피봇 = k번째 작은 숫
자
5.else Selection(A, p+1, right, k-S-1) // Large group에서 찾기
입력 크기가 n에서부터 3/4배로 연속적으로 감소되고, 크기가 1일 때에는 더 이상 분할할 수 없다.
평균 2회에 good 분할이 되므로 2xO(n) = O(n)
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
static inline void swap(int* a, int* b) { int t = *a; *a = *b; *b = t; }
/* pivot = A[left] 로 두고 Lomuto 스타일로 분할 */
int partition_left_pivot(int A[], int left, int right) {
int pivot = A[left];
int i = left + 1;
for (int j = left + 1; j <= right; ++j) {
if (A[j] < pivot) {
swap(&A[i], &A[j]);
++i;
}
}
swap(&A[left], &A[i - 1]); // 피봇을 최종 위치로
return i - 1; // 피봇 인덱스 p (A[left..p-1] < pivot, A[p+1..right] >= pivot)
}
/* Selection(A, left, right, k): A[left..right]에서 k번째(1-based) 작은 원소 반환 */
int Selection(int A[], int left, int right, int k) {
// 전제: 1 <= k <= right-left+1
// (필요하면 호출부에서 검증)
if (left == right) return A[left];
// 1) 랜덤 피봇 선택 후 A[left]와 교환
int r = left + rand() % (right - left + 1);
swap(&A[left], &A[r]);
// 2) 분할 수행 (피봇을 A[p]로 이동)
int p = partition_left_pivot(A, left, right);
// 3) Small group 크기 S = p - left
int S = p - left;
if (k <= S) {
return Selection(A, left, p - 1, k);
}
else if (k == S + 1) {
return A[p];
}
else {
return Selection(A, p + 1, right, k - S - 1);
}
}
/* 사용 예시 */
int main(void) {
srand((unsigned)time(NULL));
int A[] = { 9, 1, 5, 3, 7, 2, 8, 6, 4, 5 };
int n = sizeof(A) / sizeof(A[0]);
int k;
printf("k (1..%d): ", n);
if (scanf_s("%d", &k) != 1 || k < 1 || k > n) {
fprintf(stderr, "유효한 k를 입력하세요.\n");
return 1;
}
int ans = Selection(A, 0, n - 1, k);
printf("%d번째 작은 원소 = %d\n", k, ans);
return 0;
}
nC2 = n(n-1)/2
n=5이면, 5(5-1)/2 = 10
n(n-1)/2 = O(n2)
한 쌍의 거리 계산은 O(1)
시간 복잡도는 O(n2)xO(1) = O(n2)
n개의 점을 1/2로 분할하여 각각의 부분 문제에서 최근접 점의 쌍을 찾고, 2개의 부분 해 중에서 짧은 거리를 가진 점의 쌍을 일단 찾는다.
10과 15 중에서 짧은 거리인 10 이내의 중간 영역 안에 있는 점들 중에 더 근접한 점의 쌍이 있는지 확인해야함
중간 영역에 속한 점 = {왼쪽 부분 문제의 가장 오른쪽 점(왼쪽 중간점)의 x-좌표에서 d를 뺀 값과 오른쪽 부분 문제의 가장 왼쪽 점 (오른쪽 중간점)의 x-좌표에 d를 더한 값 사이의 x-좌표 값을 가진 점들}
d=10이면, 점 (25,-), (26,-), (28,-), (30,-), (37,-)이 중간 영역에 속한다.
ClosestPair(S)
입력: x-좌표의 오름차순으로 정렬된 배열 S에 있는 i개의 점. 단, 각 점은
(x,y)로 표현
출력: S에 있는 점들 중 최근접 점의 쌍의 거리
1. if (i ≤ 3) return (2 또는 3개의 점들 사이의 최근접 쌍)
2. 정렬된 S를 같은 크기의 SL과 SR로 분할한다. |S|가 홀수이면, |SL| =
|SR|+1이 되도록 분할
3. CPL = ClosestPair(SL) // CPL은 SL에서의 최근접 점의 쌍
4. CPR = ClosestPair(SR) // CPR은 SR에서의 최근접 점의 쌍
5. d = min{dist(CPL), dist(CPR)}일 때, 중간 영역에 속하는 점들 중에
서 최근접 점의 쌍을 찾아서 이를 CPC라고 하자. 단, dist()는 두 점 사
이의 거리
6. return (CPL, CPC, CPR 중에서 거리가 가장 짧은 쌍)
Line 1: S의 점의 수 > 3이므로 다음 line을 수행
Line 2: S를 SL과 SR로 분할
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
typedef struct { double x, y; } Point;
static inline double dist(Point a, Point b) {
double dx = a.x - b.x, dy = a.y - b.y;
return sqrt(dx*dx + dy*dy);
}
static int cmp_y(const void *a, const void *b) {
double dy = ((Point*)a)->y - ((Point*)b)->y;
return (dy > 0) - (dy < 0);
}
static double brute(Point *S, int l, int r) {
double best = INFINITY;
for (int i = l; i < r; ++i)
for (int j = i + 1; j <= r; ++j)
if ((best = fmin(best, dist(S[i], S[j]))) == 0.0) return 0.0;
return best;
}
/* strip[]는 |x - midx| < d 범위의 점들을 담고, y로 정렬 후
각 점에서 최대 다음 7개까지만 확인 */
static double stripClosest(Point *strip, int m, double d) {
qsort(strip, m, sizeof(Point), cmp_y);
double best = d;
for (int i = 0; i < m; ++i) {
for (int j = i + 1; j < m && (strip[j].y - strip[i].y) < best && j <= i + 7; ++j) {
double dij = dist(strip[i], strip[j]);
if (dij < best) best = dij;
}
}
return best;
}
/* S는 x-좌표 기준 오름차순 정렬되어 있다고 가정 */
double ClosestPair(Point *S, int l, int r) {
int n = r - l + 1;
if (n <= 3) return brute(S, l, r);
int mid = (l + r) / 2;
double midx = S[mid].x;
double dl = ClosestPair(S, l, mid);
double dr = ClosestPair(S, mid + 1, r);
double d = fmin(dl, dr);
// 중간 스트립 구성
Point *strip = (Point*)malloc(sizeof(Point) * n);
int m = 0;
for (int i = l; i <= r; ++i)
if (fabs(S[i].x - midx) < d) strip[m++] = S[i];
double ds = stripClosest(strip, m, d);
free(strip);
return fmin(d, ds);
}
/* 데모: 입력이 x로 정렬되어 있지 않다면 x 기준 정렬 후 호출 */
static int cmp_x(const void *a, const void *b) {
double dx = ((Point*)a)->x - ((Point*)b)->x;
return (dx > 0) - (dx < 0);
}
int main(void) {
Point S[] = {
{2.1, 3.4}, {12.3, 30.2}, {40.0, 50.0}, {5.0, 1.0}, {12.0, 10.0}, {3.0, 4.0}
};
int n = sizeof(S)/sizeof(S[0]);
qsort(S, n, sizeof(Point), cmp_x); // x 오름차순 정렬 (요구조건 맞추기)
double ans = ClosestPair(S, 0, n - 1);
printf("최근접 쌍 거리 = %.6f\n", ans);
return 0;
}
F(2)를 5번이나 중복하여 계산해야 하고, F(3)은 3번 계산된다.
FibNumber(n)
1. F[0]=0
2. F[1]=1
3. for i=2 to n
4. F[i] = F[i-1]+ F[i-2]