Sorting의 종류
Bubble Sort
void bubble_sort(vector<int>& v)
{
int size = static_cast<int>(v.size());
for (int i = 0; i < size - 1; ++i)
{
bool swapped = false;
// 매번 탐색한 범위에서 가장 큰 element를 맨 뒤로 밀어냄
// => 비교할 범위를 줄임
for (int j = 0; j < size - i - 1; ++j)
{
if (v[j] > v[j + 1])
{
swapped = true;
swap(v[j], v[j + 1]);
}
}
// done sorting
if (!swapped)
break;
}
}
Selection Sort
void selection_sort(vector<int>& v)
{
// 탐색한 범위 중 가장 작은 element를 맨 앞으로 보냄
int size = static_cast<int>(v.size());
for (int i = 0; i < size - 1; ++i)
{
int min = i;
// 가장 작은 원소의 index 찾음
for (int j = i; j < size; ++j)
{
if (v[min] > v[j])
min = j;
}
// 맨 앞서부터 가장 작은 원소의 index와 swap
if (min != i)
swap(v[min], v[i]);
}
}
Insertion Sort
// 가상의 sorted와 unsorted로 구간을 나누어 정렬
void insertion_sort(vector<int>& v)
{
int size = static_cast<int>(v.size());
for (int i = 1; i < size ; ++i)
{
// 선택한 원소가 적정 위치에 갈 때까지 체크하고 swap
for (int j = i ; j > 0; --j)
{
if (v[j] < v[j - 1])
swap(v[j], v[j - 1]);
}
// 비교가 끝나면 다음 i로 옮겨 반복...
}
}
Merge Sort
// divide를 먼저 실행한 뒤 -> conquer하는 과정에 sorting
void merge_sort(vector<int>& v, int l, int r)
{
if (l < r)
{
int mid = (l + r) / 2;
// divide
merge_sort(v, l, mid);
merge_sort(v, mid + 1, r);
// conquer
int i = 0, j = 0, k = l,
n1 = mid - l, n2 = r - (mid + 1);
// 두 갈래로 나누어 복사
vector<int> left(v.begin() + l, v.begin() + mid + 1),
right(v.begin() + mid + 1, v.begin() + r + 1);
// left와 right의 원소를 순서대로 비교하며 배열 v로 값 복사
while (i <= n1 && j <= n2)
{
if (left[i] < right[j])
v[k++] = left[i++];
else
v[k++] = right[j++];
}
// 위 과정에서 한쪽 배열이 먼저 끝날 경우,
// 나머지 배열을 복사
while (i <= n1)
v[k++] = left[i++];
while (j <= n2)
v[k++] = right[j++];
}
}
Quick Sort
// divide 하는 과정에서 pivot을 기준으로 swap을 통해 sorting
void quick_sort(vector<int>& v, int l, int r)
{
int i = l, j = r;
// 축을 정하고 그를 기점으로 swap
int mid = v[(l + r) / 2];
while (i <= j)
{
while (v[i] < mid)
++i;
while (mid < v[j])
--j;
if (i <= j)
{
swap(v[i], v[j]);
++i, --j;
}
}
if (l < j) quick_sort(v, l, j);
if (i < r) quick_sort(v, i, r);
}
Locality (지역성)
- CPU가 짧은 시간 범위 내에 일정 구간의 메모리 영역을 반복적으로 엑세스하는 경향
- 메모리 정보를 균일하게 엑세스 하는 것이 아님
- 짧은 시간 내에 특정 부분을 집중적으로 참조
- 함수 설계 및 사용처를 보고 특정 메모리를 캐시에 올려 재사용할 수 있음
- 현재 프로세스의 실행 패턴을 보고 가까운 미래에 프로세스의 코드와 데이터를 합리적으로 사용하도록 예측
- 자주 사용되는 페이지를 물리 메모리와 캐시 메모리에 둠
- 지역성의 정도가 떨어지면 캐시에 접근하지 않고 메인 메모리로 접근
- = 캐시 미스
quick sort vs. merge sort
merge sort
quick sort
- merge sort는 정렬할 전 구간에 걸쳐 엑세스
- quick sort는 divide한 구간을 차례로 반복적으로 엑세스
- 참조의 지역성
- 대부분의 프로그램들은 모두 단시간에 반복적으로 접근하는 데이터가 존재
- 이 과정을 효율적으로 수행하기 위해 캐시 사용
- quick sort이 merge sort보다 더 참조의 지역성 성질을 띰
- 캐시의 도움을 더 받음
Quick Sort의 Worst Case
- Quick sort는 피벗을 기준으로 left와 right로 divide 하는 로직
- divide를 총 원소의 개수만큼 하는 경우
- 이미 정렬이 되어있는 경우
Radix Sort 기수 정렬
- 낮은 자리 수(digit) 부터 정렬하는 알고리즘
- 비교 연산을 하지 않고, 정렬 속도가 빠르나 메모리가 더 많이 필요함
- 시간 복잡도는 O(dn)
- 자리수가 고정되어 있어서 안정성이 있는 정렬 방식
void radix_sort(int array[], int size){
vector<queue<int>> queues;
queues.emplace_back(10);
int max = array[0];
int digit = 0;
int factor = 1;
// 최대 자리수 계산
for(int i=1; i<size; i++)
{
if(max<array[i])
max = array[i];
}
for(int i=max; i>0;i/=10)
{
digit++;
}
// 정렬해야할 원소 중 최대 자리수까지 반복
for(int i =0; i<digit; i++)
{
// 0~9까지 돌아가며 반복
for(int j=0; j<10; j++)
{
// 원소들을 돌아가며 반복
for(int k=0; k<size; k++)
{
// 자리수가 동일한 원소를 찾으면 put
if((array[k] / factor) % 10 == j)
{
q[j].push(array[k]);
}
}
}
factor *=10;
// 자리수 별 queue에 들어가 있는 원소들을 차례대로 정렬
for (auto& q: queues)
{
int qSize = q.size();
for(int i=0; i < qSize; i++){
array[i] = q.pop();
}
}
}
}
Shell Sorting 쉘 정렬
- Insertion Sorting의 확장 버전
- Insertion이 이웃한 원소끼리 비교를 하는데 비해, Shell은 일정 간격끼리 비교
- 시간 복잡도는 O(n)
- 평균의 경우 O(n^1.5)
- 최악의 경우 O(n^2)
void shell_sort(int a[], int size) {
int i, j, temp;
// 간격 세팅
int gap = size / 2;
while( gap > 0 ) {
for( i=gap; i<size; i++ ) {
temp = a[i];
j = i;
// 일정 간격뒤에 떨어진 원소와 비교
while( j>=gap && a[j-gap]>temp ) {
// 앞쪽 원소가 더 크다면 swap
a[j] = a[j-gap];
j -= gap;
}
a[j] = temp;
}
// 더 작은 간격으로 gap 업데이트
gap /= 2;
}
}
int main() {
int arr[] = {9,1,22,4,0,-1,1,22,100,10};
int size = sizeof(arr)/sizeof(int);
shell_sort(arr, size);
for(int x: arr) std::cout << x << " ";
// -1 0 1 1 4 9 10 22 22 100
}
Heap Sorting 힙 정렬
- 최소 힙 또는 최대 힙을 구현해 정렬
- 최대, 혹은 최소 값을 차례대로 삭제하면서 pop 되는 원소들을 차례대로 정렬
- 힙 설명
정렬 별 장단점
정렬 별 시간 / 공간 복잡도
출처
- https://gmlwjd9405.github.io/2018/05/08/algorithm-merge-sort.html
- https://gmlwjd9405.github.io/2018/05/10/algorithm-quick-sort.html
- https://medium.com/pocs/locality%EC%9D%98-%EA%B4%80%EC%A0%90%EC%97%90%EC%84%9C-quick-sort%EA%B0%80-merge-sort%EB%B3%B4%EB%8B%A4-%EB%B9%A0%EB%A5%B8-%EC%9D%B4%EC%9C%A0-824798181693
- https://parkdream.tistory.com/115
- https://coding-factory.tistory.com/615
- https://stackoverflow.com/questions/26538245/quicksort-worst-case-condition