알고리즘과 자료구조: 중급 정렬
중급 정렬을 이해할 수 있다.
알고리즘과 자료구조: 중급 정렬
📌개요
더 효율적인 정렬 알고리즘을 이해하기 위해 중급 정렬 알고리즘을 살펴본다.
📌내용
Heap Sort
,Merge Sort
,Quick Sort
같은 중급 정렬 알고리즘은 실무에서도 많이 사용되며, 고급 정렬 알고리즘을 이해하는 데 중요한 개념을 제공한다.
병합 정렬(Merge Sort)
시간 복잡도: O(n log n)
분할 정복(Divide and Conquer) 방식을 사용하여 배열을 반씩 나누고 병합하며 정렬하는 알고리즘이다.
특징
- 안정적인 정렬 (Stable Sort) → 동일한 값의 상대적 순서 유지
- 외부 정렬(External Sort)에 적합하다. 디스크나 네트워크에서 데이터를 읽으며 정렬 가능하다.
- 데이터가 이미 정렬된 경우에도 성능을 유지한다.
- 연결 리스트(Linked List) 정렬에 유리하다. 연속된 메모리 공간을 필요로 하지 않는다.
실무 사용 예시
- 안정성이 중요한 경우에 사용한다.
- 데이터베이스에서 인덱스 정렬 등
- 대용량 데이터 정렬.
- 외부 정렬 알고리즘에서 많이 사용한다.
- 연결 리스트 정렬
- 배열보다 메모리 재배열이 어려운 경우
Pseudo Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Merge(left, right):
result = empty array
i = 0, j = 0
while i < length(left) and j < length(right):
if left[i] < right[j]:
append left[i] to result
increment i
else:
append right[j] to result
increment j
append remaining elements of left to result (if any)
append remaining elements of right to result (if any)
return result
MergeSort(A, left, right):
if left < right:
mid = (left + right) / 2
MergeSort(A, left, mid)
MergeSort(A, mid + 1, right)
Merge(A, left, mid, right)
JavaScript Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function merge(left, right) {
let result = [];
let i = 0, j = 0;
while (i < left.length && j < right.length) {
if (left[i] < right[j]) {
result.push(left[i]);
i++;
} else {
result.push(right[j]);
j++;
}
}
return result.concat(left.slice(i), right.slice(j));
}
function mergeSort(A) {
if (A.length < 2) return A;
let mid = Math.floor(A.length / 2);
let left = mergeSort(A.slice(0, mid));
let right = mergeSort(A.slice(mid));
return merge(left, right);
}
퀵 정렬(Quick Sort)
시간 복잡도: 평균 O(n log n), 최악 O(n^2)
피벗(Pivot)을 기준으로 작은 값과 큰 값으로 나누어 정렬하는 알고리즘이다.
QuickSort
함수는 배열을 재귀적으로 나누어 정렬한다.Partition
함수는pivot
을 기준으로 배열을 두 두분으로 분할하는 과정이다.pivot
보다 작은 값들은 왼쪽, 큰 값들은 오른쪽으로 이동시킨 후 새로운pivot
을 반환한다.
특징
- 비교 기반 정렬 중 가장 빠른 평균 속도
- 추가적인 메모리가 거의 필요 없다.
- 데이터가 랜덤하게 분포되어 있을 때 성능이 뛰어나다
- 병합 정렬보다 캐시 친화적이다.
실무 사용 예시
- 대용량 데이터 정렬
- 일반적인 데이터 정렬 (데이터가 랜덤 분포일 경우)
정렬된 배열을 받는 경우 최악의 성능을 자랑한다. 최악의 케이스를 모두 피할 순 없지만,
pivot
을 작은 값 또는 큰 값이 아닌 중간 값 또는 랜덤 선택 등으로 지정하는 방법을 사용해볼 수 있다.
Pseudo Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Partition(A, low, high):
pivot = A[high]
i = low - 1
for j = low to high - 1:
if A[j] < pivot:
i += 1
swap(A[i], A[j])
swap(A[i + 1], A[high])
return i + 1
QuickSort(A, low, high):
if low < high:
pivotIndex = Partition(A, low, high)
QuickSort(A, low, pivotIndex - 1)
QuickSort(A, pivotIndex + 1, high)
JavaScript Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function partition(A, low, high) {
let pivot = A[high];
let i = low - 1;
for (let j = low; j < high; j++) {
if (A[j] < pivot) {
i++;
[A[i], A[j]] = [A[j], A[i]];
}
}
[A[i + 1], A[high]] = [A[high], A[i + 1]];
return i + 1;
}
function quickSort(A) {
if (A.length <= 1) return A;
let pivotIndex = partition(A, 0, A.length - 1);
return [
...quickSort(A.slice(0, pivotIndex)),
A[pivotIndex],
...quickSort(A.slice(pivotIndex + 1)),
];
}
힙 정렬(Heap Sort)
시간 복잡도: O(n log n)
힙(Heap) 자료구조를 활용하여 최댓값 또는 최솟값을 빠르게 찾는 방식으로 정렬하는 알고리즘이다.
특징
- 최악의 경우에도 동일한 시간 복잡도를 보장한다.
- 추가적인 메모리 사용이 거의 없다.
- 우선순위 큐에서 응용 가능하다.
실무 사용 예시
- 우선순위 큐 기반의 정렬
- 최소/최대 힙을 이용한 정렬
- 실시간 데이터 스트림 정렬
- 항상 정렬된 상태를 유지해야 하는 경우
- 메모리 사용을 최소화해야 하는 정렬
Pseudo Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
HEAP_SORT_ASCENDING(A):
BUILD_MAX_HEAP(A)
FOR i FROM length(A) - 1 DOWNTO 1:
SWAP(A[0], A[i])
MAX_HEAPIFY(A, 0, i)
RETURN A
BUILD_MAX_HEAP(A):
FOR i FROM FLOOR(length(A) / 2) - 1 DOWNTO 0:
MAX_HEAPIFY(A, i, length(A))
MAX_HEAPIFY(A, i, heapSize):
largest ← i
left ← 2 * i + 1
right ← 2 * i + 2
IF left < heapSize AND A[left] > A[largest]:
largest ← left
IF right < heapSize AND A[right] > A[largest]:
largest ← right
IF largest ≠ i:
SWAP(A[i], A[largest])
MAX_HEAPIFY(A, largest, heapSize)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
HEAP_SORT_DESCENDING(A):
BUILD_MIN_HEAP(A)
FOR i FROM length(A) - 1 DOWNTO 1:
SWAP(A[0], A[i])
MIN_HEAPIFY(A, 0, i)
RETURN A
BUILD_MIN_HEAP(A):
FOR i FROM FLOOR(length(A) / 2) - 1 DOWNTO 0:
MIN_HEAPIFY(A, i, length(A))
MIN_HEAPIFY(A, i, heapSize):
smallest ← i
left ← 2 * i + 1
right ← 2 * i + 2
IF left < heapSize AND A[left] < A[smallest]:
smallest ← left
IF right < heapSize AND A[right] < A[smallest]:
smallest ← right
IF smallest ≠ i:
SWAP(A[i], A[smallest])
MIN_HEAPIFY(A, smallest, heapSize)
JavaScript Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
function heapSortAscending(A) {
let n = A.length;
buildMaxHeap(A);
for (let i = n - 1; i > 0; i--) {
[A[0], A[i]] = [A[i], A[0]];
maxHeapify(A, 0, i);
}
return A;
}
function heapSortDescending(A) {
let n = A.length;
buildMinHeap(A);
for (let i = n - 1; i > 0; i--) {
[A[0], A[i]] = [A[i], A[0]];
minHeapify(A, 0, i);
}
return A;
}
function buildMaxHeap(A) {
let heapSize = A.length;
for (let i = Math.floor(heapSize / 2) - 1; i >= 0; i--) {
maxHeapify(A, i, heapSize);
}
}
function buildMinHeap(A) {
let heapSize = A.length;
for (let i = Math.floor(heapSize / 2) - 1; i >= 0; i--) {
minHeapify(A, i, heapSize);
}
}
function maxHeapify(A, i, heapSize) {
let largest = i;
let left = 2 * i + 1;
let right = 2 * i + 2;
if (left < heapSize && A[left] > A[largest]) {
largest = left;
}
if (right < heapSize && A[right] > A[largest]) {
largest = right;
}
if (largest !== i) {
[A[i], A[largest]] = [A[largest], A[i]];
maxHeapify(A, largest, heapSize);
}
}
function minHeapify(A, i, heapSize) {
let smallest = i;
let left = 2 * i + 1;
let right = 2 * i + 2;
if (left < heapSize && A[left] < A[smallest]) {
smallest = left;
}
if (right < heapSize && A[right] < A[smallest]) {
smallest = right;
}
if (smallest !== i) {
[A[i], A[smallest]] = [A[smallest], A[i]];
minHeapify(A, smallest, heapSize);
}
}
📌적절한 사용 전략
- 빠른 정렬이 필요하면? → 퀵 정렬 (일반적인 경우)
- 안정적인 정렬이 필요하면? → 병합 정렬 (데이터 정렬 순서를 유지해야 할 때)
- 메모리를 절약하면서 안정적인 성능을 원하면? → 힙 정렬
정렬 알고리즘 | 평균 시간 복잡도 | 최악 시간 복잡도 | 공간 복잡도 | 안정성 | 특징 및 사용 사례 |
---|---|---|---|---|---|
퀵 정렬 (Quick Sort) | O(nlogn) | O(n2) | O(logn) | ❌ | 가장 빠른 평균 속도, 제자리 정렬, 랜덤 데이터 정렬 |
병합 정렬 (Merge Sort) | O(nlogn) | O(nlogn) | O(n) | ✅ | 안정적 정렬, 외부 정렬 및 연결 리스트 정렬 |
힙 정렬 (Heap Sort) | O(nlogn) | O(nlogn) | O(1) | ❌ | 우선순위 큐 기반 정렬, 메모리 절약 필요 시 |
⚙️EndNote
Pseudo Code
Pseudo Code
는 실제 프로그래밍 언어의 구문과 유사하지만, 특정 프로그래밍 언어의 문법에 얽매이지 않고 알고리즘의 논리적인 흐름을 설명하는 데 사용되는 간단한 서술 형태다.
- 언어 독립성: 특정 프로그래밍 언어의 문법을 따르지 않으며, 누구나 쉽게 읽고 이해할 수 있다.
- 간결함: 복잡한 문법 요소를 생략하고 핵심 알고리즘 로직만을 표현한다.
- 가독성: 사람이 읽기 쉽게 작성되어, 알고리즘의 흐름과 동작을 쉽게 파악할 수 있다.
This post is licensed under CC BY 4.0 by the author.