선형 시간 선택 알고리즘
은 정렬되지 않은 데이터에서 원하는 순서의 요소(예: k번째로 작은 요소)를 선형 시간 내에 찾는 알고리즘이다. 일반적으로 정렬 알고리즘을 사용하여 데이터를 정렬한 후 원하는 요소를 찾을 수 있지만, 이는 보통 $O(n log n)$ 의 시간 복잡도를 갖는다. 그러나 선형 시간 선택 알고리즘은 정렬되지 않은 상태에서 $O(n)$ 의 시간 복잡도로 원하는 요소를 찾을 수 있다.
이 알고리즘은 퀵 정렬(Quick Sort)
의 분할 방식을 활용한다. 주어진 배열에서 임의의 피벗(pivot)
을 선택하고, 이 피벗을 기준으로 배열을 분할한다. 피벗보다 작은 요소들은 왼쪽 부분 배열에, 큰 요소들은 오른쪽 부분 배열에 위치시킨다. 이때 피벗의 정확한 위치(인덱스)를 알 수 있으며, 이를 통해 원하는 k번째 요소가 피벗의 위치와 비교하여 어느 부분 배열에 존재하는지 판단할 수 있다.
int select(int *arr, int left, int right, int k) {
if (k >= 0 && k < right - left + 1) {
int n = right - left + 1;
int pivot = arr[left + rand() % n];
// 분할 과정
int pivot_idx = partition(arr, left, right, pivot);
// 피벗의 위치와 k 비교
if (pivot_idx - left == k)
return arr[pivot_idx];
else if (pivot_idx - left > k)
return select(arr, left, pivot_idx - 1, k);
else
return select(arr, pivot_idx + 1, right, k - (pivot_idx - left + 1));
}
return INT_MAX; // 오류 처리
}
select
int select(int *arr, int left, int right, int k)
이 함수는 배열 arr
에서 left
부터 right
까지의 범위에서 k번째로 작은 요소를 찾는 함수이다.
매개변수:
arr
: 대상 배열left
: 탐색 범위의 시작 인덱스right
: 탐색 범위의 끝 인덱스k
: 찾고자 하는 순서 (0-based index)기본 조건 검사:
if (k >= 0 && k < right - left + 1)
를 통해 k의 유효성을 확인한다.피벗 선택:
int n = right - left + 1;
을 통해 현재 부분 배열의 크기를 계산한다.int pivot = arr[left + rand() % n];
을 사용하여 부분 배열 내에서 임의의 피벗을 선택한다.분할 과정:
int pivot_idx = partition(arr, left, right, pivot);
을 호출하여 배열을 피벗을 기준으로 분할하고, 피벗의 최종 위치를 얻는다.재귀 호출 및 반환:
if (pivot_idx - left == k)
:
arr[pivot_idx]
가 k번째로 작은 요소이다.else if (pivot_idx - left > k)
:
else
:
오류 처리:
INT_MAX
를 반환하여 오류를 나타낸다.partition
partition
함수는 퀵 정렬에서 사용하는 분할 함수로, 피벗을 기준으로 배열을 재배열한다. 코드 구현은 다음과 같다.
int partition(int *arr, int left, int right, int pivot) {
while (left <= right) {
while (arr[left] < pivot)
left++;
while (arr[right] > pivot)
right--;
if (left <= right) {
swap(arr[left], arr[right]);
left++;
right--;
}
}
return left - 1;
}
기능:
작동 원리:
left
와 right
를 사용하여 배열의 양 끝에서부터 탐색한다.arr[left] < pivot
인 동안 left
를 증가시킨다.arr[right] > pivot
인 동안 right
를 감소시킨다.left
와 right
를 각각 이동시킨다.선형 시간 선택 알고리즘은 퀵 정렬의 분할 방식을 활용하여 원하는 순서의 요소를 찾는다. 이제 수학적 귀납법을 통해 알고리즘의 정확성을 증명해보자.
명제 P(n): 배열 크기가 n일 때, select
함수는 k번째로 작은 요소를 정확히 찾아낸다.
select
함수는 즉시 해당 요소를 반환하므로 명제 P(1)은 참이다.배열 크기가 n인 경우:
피벗 선택 및 분할:
partition
함수를 통해 배열을 분할한다.pivot_idx
를 얻는다.피벗의 위치와 k 비교:
pivot_idx - left == k
인 경우, 피벗이 k번째로 작은 요소이므로 반환한다.pivot_idx - left > k
인 경우, 왼쪽 부분 배열에서 탐색한다.pivot_idx - left < k
인 경우, 오른쪽 부분 배열에서 새로운 k 값을 조정하여 탐색한다.재귀 호출의 유효성:
**따라서, 배열 크기가 n인 경우에도 select
함수는 정확한 결과를 반환한다.
select
알고리즘은 항상 k번째로 작은 요소를 정확히 찾아낸다.선형 시간 선택 알고리즘의 시간 복잡도를 분석해보자.
O(n)
의 시간 복잡도를 갖는다.
O(n^2)
이다.
O(n)
이다.무작위 피벗 선택:
Median of Medians 알고리즘:
Median of Medians
알고리즘을 사용하면 최악의 경우에도 O(n)
의 시간 복잡도를 보장할 수 있다.선형 시간 선택 알고리즘
은 퀵 정렬의 분할 방식을 활용하여 정렬되지 않은 배열에서 원하는 순서의 요소를 효율적으로 찾을 수 있다. 평균적으로 O(n)
의 시간 복잡도를 가지며, 추가적인 메모리 공간을 필요로 하지 않는다. 무작위 피벗 선택을 통해 최악의 경우를 피할 수 있으며, 큰 데이터 집합에서 효율적으로 동작한다. 그러나 최악의 경우 O(n^2)
의 시간 복잡도를 가질 수 있으므로, 이러한 상황을 방지하기 위한 추가적인 전략이 필요할 수 있다.