본 시리즈는 프로그래밍 알고리즘의 개념을 정리하고 실습을 진행해보는 시리즈입니다.
분할 정복 기반 정렬 알고리즘은 문제를 작은 부분으로 나누고, 각각을 정렬한 후 다시 합쳐 전체 문제를 해결하는 방식입니다.
이번 포스팅에서는 퀵 정렬, 듀얼 피벗 퀵 정렬을 다루고, 다음 포스팅에서 합병 정렬과 팀 정렬을 다룹니다.
퀵 정렬 (Quick Sort)은 분할 정복 (Divide and Conquer) 기법을 사용하여 배열을 정렬하는 알고리즘입니다.
퀵 정렬의 주요 특징:
퀵 정렬은 세 단계로 이루어집니다:
동작 예시:
주어진 배열 [5, 2, 1, 9, 4]
을 퀵 정렬로 정렬하는 과정:
4
를 피벗으로 선택합니다.4
를 기준으로 작은 값은 왼쪽, 큰 값은 오른쪽으로 나누면 [2, 1] | 4 | [5, 9]가 됩니다.1
을 피벗으로 선택, [1] | 2로 정렬 완료.9
을 피벗으로 선택, [5] | 9로 정렬 완료.import java.util.Arrays;
public class QuickSort {
// 퀵 정렬 메서드
public static void quickSort(int[] arr, int low, int high) {
if (low < high) {
// 분할 지점
int pi = partition(arr, low, high);
// 좌측과 우측 파티션에 대해 재귀적으로 정렬 수행
quickSort(arr, low, pi - 1);
quickSort(arr, pi + 1, high);
}
}
// 배열을 분할하는 메서드
public static int partition(int[] arr, int low, int high) {
int pivot = arr[high]; // 마지막 요소를 피벗으로 선택
int i = (low - 1); // 작은 요소의 인덱스
for (int j = low; j < high; j++) {
// 현재 요소가 피벗보다 작으면 교환
if (arr[j] < pivot) {
i++;
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
// 피벗을 적절한 위치로 이동
int temp = arr[i + 1];
arr[i + 1] = arr[high];
arr[high] = temp;
return i + 1;
}
public static void main(String[] args) {
int[] arr = {9, 2, 1, 5, 4};
quickSort(arr, 0, arr.length - 1);
System.out.println("Sorted array: " + Arrays.toString(arr));
}
}
Java 구현 설명:
partition
메서드는 배열을 분할하고, 피벗을 적절한 위치에 놓습니다.quickSort
메서드는 분할된 배열의 좌측과 우측 부분에 대해 재귀적으로 정렬을 수행합니다.출력 예시:
Sorted array: [1, 2, 4, 5, 9]
def quick_sort(arr, low, high):
if low < high:
# 분할 지점
pi = partition(arr, low, high)
# 좌측과 우측 파티션에 대해 재귀적으로 정렬 수행
quick_sort(arr, low, pi - 1)
quick_sort(arr, pi + 1, high)
def partition(arr, low, high):
pivot = arr[high] # 마지막 요소를 피벗으로 선택
i = low - 1 # 작은 요소의 인덱스
for j in range(low, high):
if arr[j] < pivot:
i += 1
arr[i], arr[j] = arr[j], arr[i] # 교환
arr[i + 1], arr[high] = arr[high], arr[i + 1] # 피벗을 적절한 위치로 이동
return i + 1
# 예시 배열
arr = [9, 2, 1, 5, 4]
quick_sort(arr, 0, len(arr) - 1)
print("Sorted array:", arr)
Python 구현 설명:
partition
함수는 피벗을 기준으로 배열을 분할합니다.quick_sort
함수는 분할된 배열의 좌측과 우측 부분에 대해 재귀적으로 정렬을 수행합니다.출력 예시:
Sorted array: [1, 2, 4, 5, 9]
Dual-Pivot Quick Sort는 퀵 정렬(Quick Sort)의 변형된 알고리즘으로, 두 개의 피벗을 사용하여 배열을 세 부분으로 나누어 정렬하는 방식입니다.
피벗1
보다 작은 값들피벗1
과 피벗2
사이의 값들피벗2
보다 큰 값들이 알고리즘은 비교 횟수를 줄일 수 있어 일반적인 퀵 정렬보다 더 효율적일 수 있으며, 특히 Java의 Arrays.sort() 메서드에서 대규모 기본형 배열을 정렬할 때 사용됩니다.
시간 복잡도:
특징:
Dual-Pivot Quick Sort는 다음과 같은 단계로 동작합니다:
피벗1
보다 작은 값들은 왼쪽으로 이동.피벗1
과 피벗2
사이의 값들은 중간에 위치.피벗2
보다 큰 값들은 오른쪽으로 이동.피벗1
과 피벗2
를 기준으로 정렬된 세 구간이 합쳐지며 완료됩니다.동작 예시:
주어진 배열 [2, 9, 1, 5, 4, 7, 8, 3, 6]
을 듀얼 피벗 퀵 정렬로 정렬하는 과정:
피벗 선택: 배열의 첫 번째 요소 2
와 마지막 요소 6
을 두 개의 피벗으로 선택합니다.
2
6
세 부분으로 분할:
[1]
[5, 4, 3]
[9, 7, 8]
재귀적으로 정렬:
[1]
과 피벗1 [2]
은 이미 정렬된 상태이므로 더 이상 정렬하지 않습니다.[5, 4, 3]
에 대해 Dual-Pivot Quick Sort를 다시 적용:3
, 피벗2: 5
[4]
는 3
과 5
사이에 위치하며 재귀적으로 정렬이 완료됩니다.[9, 7, 8]
에 대해 재귀적으로 Dual-Pivot Quick Sort 적용:7
, 피벗2: 9
[8]
은 7
과 9
사이에 위치하며 재귀적으로 정렬이 완료됩니다.최종 정렬 결과: 모든 부분을 정렬하고 합치면 배열은 [1, 2, 3, 4, 5, 6, 7, 8, 9]으로 정렬됩니다.
import java.util.Arrays;
public class DualPivotQuickSort {
// Dual-Pivot Quick Sort 구현
public static void dualPivotQuickSort(int[] arr, int low, int high) {
if (low < high) {
// 두 개의 피벗을 선택
int[] pivots = partition(arr, low, high);
int pivot1 = pivots[0];
int pivot2 = pivots[1];
// 피벗1보다 작은 부분, 피벗1과 피벗2 사이의 부분, 피벗2보다 큰 부분 각각 정렬
dualPivotQuickSort(arr, low, pivot1 - 1);
dualPivotQuickSort(arr, pivot1 + 1, pivot2 - 1);
dualPivotQuickSort(arr, pivot2 + 1, high);
}
}
// 피벗으로 배열을 세 구간으로 분할하는 함수
private static int[] partition(int[] arr, int low, int high) {
if (arr[low] > arr[high]) {
swap(arr, low, high); // 피벗1과 피벗2를 정렬
}
int pivot1 = arr[low];
int pivot2 = arr[high];
int i = low + 1;
int j = low + 1;
int k = high - 1;
while (j <= k) {
if (arr[j] < pivot1) {
swap(arr, i++, j++);
} else if (arr[j] > pivot2) {
swap(arr, j, k--);
} else {
j++;
}
}
swap(arr, low, --i); // 피벗1을 제자리에 놓음
swap(arr, high, ++k); // 피벗2를 제자리에 놓음
return new int[]{i, k}; // 피벗1과 피벗2의 위치 반환
}
// 배열 요소 교환 함수
private static void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
public static void main(String[] args) {
int[] arr = {2, 9, 1, 5, 4, 7, 8, 3, 6};
dualPivotQuickSort(arr, 0, arr.length - 1);
System.out.println("Sorted array: " + Arrays.toString(arr));
}
}
Java 구현 설명:
dualPivotQuickSort
는 두 개의 피벗을 사용해 배열을 세 부분으로 나누고, 이를 재귀적으로 정렬하는 Dual-Pivot Quick Sort 알고리즘을 구현합니다.partition
함수는 피벗1
, 피벗2
를 선택하고, 배열을 세 구간으로 분할합니다. 이후 피벗1
과 피벗2
를 각 구간에 배치하고 그 사이 부분을 다시 정렬합니다.출력 예시:
Sorted array: [1, 2, 3, 4, 5, 6, 7, 8, 9]
# 배열 요소 교환 함수
def swap(arr, i, j):
arr[i], arr[j] = arr[j], arr[i]
# 배열을 두 개의 피벗을 기준으로 세 구간으로 나누는 함수
def partition(arr, low, high):
if arr[low] > arr[high]:
swap(arr, low, high) # 피벗1과 피벗2를 정렬
pivot1 = arr[low]
pivot2 = arr[high]
i = low + 1
j = low + 1
k = high - 1
while j <= k:
if arr[j] < pivot1:
swap(arr, i, j)
i += 1
j += 1
elif arr[j] > pivot2:
swap(arr, j, k)
k -= 1
else:
j += 1 # 피벗 사이의 값에 대해 j 증가
swap(arr, low, i - 1) # 피벗1을 제자리에 놓음
swap(arr, high, k + 1) # 피벗2를 제자리에 놓음
return i - 1, k + 1 # 피벗1과 피벗2의 위치 반환
# Dual-Pivot Quick Sort 구현
def dual_pivot_quick_sort(arr, low, high):
if low < high:
pivot1, pivot2 = partition(arr, low, high)
# 피벗1보다 작은 부분, 피벗1과 피벗2 사이의 부분, 피벗2보다 큰 부분 각각 정렬
dual_pivot_quick_sort(arr, low, pivot1 - 1)
dual_pivot_quick_sort(arr, pivot1 + 1, pivot2 - 1)
dual_pivot_quick_sort(arr, pivot2 + 1, high)
# 예시 배열
arr = [2, 9, 1, 5, 4, 7, 8, 3, 6]
dual_pivot_quick_sort(arr, 0, len(arr) - 1)
print("Sorted array:", arr)
Python 구현 설명:
dual_pivot_quick_sort
는 두 개의 피벗을 기준으로 배열을 세 부분으로 나누어 재귀적으로 정렬하는 알고리즘입니다.partition
함수는 배열을 세 구간으로 나누고, 피벗을 각각의 자리에 배치한 후, 피벗을 기준으로 세 부분을 재귀적으로 정렬합니다.출력 예시:
Sorted array: [1, 2, 3, 4, 5, 6, 7, 8, 9]
분할 정복 기반 정렬 알고리즘들은 문제를 작은 단위로 분할한 후 각각을 정렬하고 다시 합쳐 전체 문제를 해결하는 방식입니다.
다음은 각 정렬 알고리즘의 시간 복잡도와 특징을 비교하여 정리한 표입니다.
정렬 알고리즘 | 평균 시간 복잡도 | 최악 시간 복잡도 | 공간 복잡도 | 안정성 | 특징 |
---|---|---|---|---|---|
Quick Sort | 불안정 | 피벗 선택에 따라 최악의 성능을 가짐 (불균형 분할의 경우) | |||
Dual-Pivot Quick Sort | 불안정 | 두 피벗을 사용하여 성능을 개선한 퀵 정렬 | |||
Merge Sort | 안정적 | 재귀 호출로 인해 추가 메모리 사용, 안정적 정렬 보장 | |||
Tim Sort | 안정적 | 실무에서 자주 사용되며, Java와 Python의 기본 정렬 알고리즘 |
Quick Sort:
Dual-Pivot Quick Sort:
Merge Sort:
Tim Sort:
분할 정복 기반 정렬 알고리즘은 대부분 의 시간 복잡도를 가지며, 데이터 특성에 따라 선택해야 합니다.
이번 포스팅에서는 퀵 정렬과 듀얼 피벗 퀵 정렬의 기본 개념과 동작 원리, 그리고 구현을 다루었습니다.
다음 포스팅에서는 합병 정렬(Merge Sort)과 팀 정렬(Tim Sort)을 다루며, 이들의 구체적인 동작 방식과 구현 방법을 소개할 예정입니다.