전 세계 컴퓨터의 70%는 정렬을 하는데 시간을 보내고 있다 할 정도로 중요하고, 값 비싼 연산인 정렬을 파헤쳐보자.
전 세계 컴퓨터 공학자들의 Bible로 불리는 C로 쓴 자료구조의 높은 퀄리티의 코드를 하나하나 씹어 먹어보자!
(Y 대학 Prof. Cho의 강의력과 학생들을 향한 헌신에 감사하며 작성하는 글입니다.)
Insertion Sort (삽입 정렬)
Time Complexity | 최선 | 평균 | 최악 | Space Complexity | Stable |
Insertion | n | n^2 | n^2 | 1 | Yes |
대부분의 데이터가 sorting 되어 있을 경우 효과적이다.
데이터가 역순으로 sorting 되었을 때 최악의 성능
데이터 수가 n개일 때 성능
최선 | 최악 | |
비교 | n | n^2 / 2 |
교환 | 0 | n^2 / 2 |
삽입 정렬의 변형
Binary Insertion Sort
이미 비교해야하는 데이터의 앞부분이 정렬되어 있기 때문에 이진 검색으로 비교를 O(n)으로 줄일 수 있지만 값을 삽입하기 위해 한 칸씩 미는 교환 연산은 줄일 수 없다.
List Insertion Sort
데이터의 위치를 변경하는 연산은 줄일 수 있지만 이진 검색으로 데이터를 비교하는 연산은 줄일 수 없다.
코드
#include <stdio.h>
void insertion_sort(int list[], int n) {
int i, j;
int next;
for (i = 1; i < n; i++) {
next = list[i];
for (j = i - 1; j >= 0 && next < list[j]; j--)
list[j + 1] = list[j];
list[j + 1] = next;
}
}
Quick Sort (퀵 정렬)
각 단계 마다 기준값(pivot) 보다 작은 부분과 큰 부분으로 데이터를 분할해 나가면서 정렬
현재 정렬 알고리즘 중에 평균 퍼포먼스가 가장 좋다.
Time Complexity | 최선 | 평균 | 최악 | Time Complexity | Stable |
Quick | n log n | n log n | n^2 | log n, or n | No |
이미 오름차순으로 정렬되어 있는 배열에서 최악의 성능
로직
1. 배열의 가장 왼쪽 데이터 list [left]를 pivot으로 설정
2. left를 i, right+1을 j 로 설정
3. i를 증가시키면서 값이 pivot 보다 작으면 stop
4. j를 감소시키면서 값이 pivot 보다 크면 stop
5. 3,4가 충족되면 i와 j 값 swap
6. 3,4,5를 반복 후 i와 j가 cross 되면 list [left]와 list [j] 값 swap

코드
#include <stdio.h>
#define SWAP(x,y,t) ((t)=(x),(x)=(y),(y)=(t))
void quicksort(int list[], int left, int right) {
int pivot, i, j;
int temp;
if (left < right) {
i = left;
j = right + 1;
pivot = list[left];
do {
do {
i++;
} while (list[i] < pivot);
do {
j--;
} while (list[j] > pivot);
if (i < j)
SWAP(list[i], list[j], temp);
} while (i < j);
SWAP(list[left], list[j], temp);
quicksort(list, left, j - 1);
quicksort(list, j + 1, right);
}
}
Merge Sort (병합 정렬)
크기가 n인 리스트를 합병하여 2n 크기 리스트를 생성해 나가며 정렬
Java에서 object 배열은 stable 해야 하기 때문에 Merge Sort를 사용함
Time Complexity | 최선 | 평균 | 최악 | Space Complexity | Stable |
Merge | n long n | n log n | n^2 | n | Yes |
로직
1. 전체의 큰 리스트를 1개씩 쪼개어 두개씩 짝지어 각각 순서대로 정렬
2. 병합한 리스트를 다시 인접한 리스트와 병합 (즉 크기가 n인 리스트를 합병하여 2n 크기 리스트 생성)
3. 위 과정을 반복적을 거쳐 최종적으로 전체 리스트를 병합
첫 번째 리스트의 index i와 두 번째 리스트 index j가 동시에 출발하여 i와 j 값중 작은 값을 sorted에 저장

merge_pass 함수에서 사용되는 인덱스 범위

코드
void merge(int list[], int sorted[], int i, int m, int n) { //정렬 된 리스트를 병합
int j = m + 1; //i: 첫 번째 리스트 index, j: 두 번째 리스트 index
int k = i; //k: sorted[] index
while (i <= m && j <= n) {
if (list[i] <= list[j])
sorted[k++] = sorted[i++];
else
sorted[k++] = list[j++];
}
if (i > m) //두 번째 리스트의 나머지를 sorted[]에 복사
for (; j <= n; j++)
sorted[k++] = list[j];
else
for (; i <= m; i++) //첫 번째 리스트의 나머지를 sorted[]에 복사
sorted[k++] = list[i];
}
void merge_pass(int list[], int sorted[], int n, int length) {
/* list[n] 배열에서 길이가 length인 인접한 한 쌍의 서브리스트를 병합하여 sorted[] 배열에 저장*/
int i, j;
for (i = 0; i + 2 * length - 1 < n; i += 2 * length)
merge(list, sorted, i, i + length - 1, i + 2 * length - 1);
//길이가 2*length 보다 적게 남아있는 서브 리스트들을 병합
if (i + length < n)
merge(list, sorted, i, i + length - 1, n - 1);
else //하나의 서브리스트만 남아있는 경우
for (j = i; j < n; j++)
sorted[j] = list[j];
}
void merge_sort(int list[], int n) {
int length = 1;
int extra[11];
while (length < n) {
merge_pass(list, extra, n, length);
length *= 2;
merge_pass(extra, list, n, length);
length *= 2;
}
}
'자료구조' 카테고리의 다른 글
[C로 쓴 자료구조] 그래프(Graph) (0) | 2022.12.08 |
---|---|
[C로 쓴 자료구조] FIFO와 우선순위를 이용한 작업 스케줄링의 비교 (0) | 2022.11.17 |
[C로 쓴 자료구조] 미로찾기 (1) | 2022.10.23 |
[C로 쓴 자료구조] 이중 연결 리스트 (Doubly Linked Lists) (0) | 2022.10.21 |
[C로 쓴 자료구조] 동치 관계 (Equivalence Relations) (0) | 2022.10.21 |