정렬 (Sorting)
정렬 |
공간 복잡도 |
시간 복잡도(최선) |
시간 복잡도(평균) |
시간 복잡도(최악) |
선택 정렬 |
n |
n2 |
n2 |
n2 |
버블 정렬 |
n |
n2 |
n2 |
n2 |
삽입 정렬 |
n |
n |
n2 |
n2 |
병합 정렬 |
2n |
nlog2n |
nlog2n |
nlog2n |
빠른 정렬 |
n |
nlog2n |
nlog2n |
n2 |
힙 정렬 |
n |
nlog2n |
nlog2n |
nlog2n |
오름차 순 정렬을 기준으로 설명
1. 선택 정렬(Selection Sort)
- 정렬된 부분과 정렬 되지 않은 부분으로 나뉜다.
- 정렬 되지 않은 값 중 가장 작은 값을 구해 현재 인덱스와 교체한다.
- 다음 인덱스에 같은 과정을 반복합니다.
1 2 3 4 5 6 7 8 9 10 11 12 13
| void selectionSort(vector<int>& v) { for(int i=0; i<v.size(); i++) { for(int j = i+1; j<v.size(); j++) { if(v[j] < v[i]) { swap(v[j], v[i]); } } } }
|
2. 버블 정렬(Bubble Sort)
- 매번 연속된 자료를 비교하여 교환하면서 자료를 정렬한다.
- 위 과정을 통해 맨 마지막에는 가장 큰 값이 저장 된다.
- 매번 마지막을 제외한 배열에 위 과정을 반복한다.
1 2 3 4 5 6 7 8 9 10 11
| void bubbleSort(vector<int>& v) { for(int i=1; i<v.size(); i++) { for(int j=0; j<v.size()-i; j++) { if(v[j] > v[j+1]) swap(v[j], v[j+1]); } } }
|
3. 삽입 정렬(Insertion Sort)
- 두 번쨰 인덱스부터 시작한다.
- 앞의 값들과 비교하여 삽입할 위치를 지정한 후 자료를 뒤로 옮기고 지정한 자리에 자료를 삽입한다.
- 위 과정을 반복한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| void insertionSort(vector<int>& v) { for(int i=1; i<v.size(); i++) { int key = v[i]; for(int j=0; j<=i; j++) { if(key < v[j]) { swap(key, v[j]); } } } }
|
4. 합병 정렬(Merge Sort)
- 분할 정복(Divide and conquer) 방식
- 배열의 크기가 1보다 작거나 같을 떄까지 분할한다.
- 두 개의 배열을 비교하여 정렬되고 합병된 배열을 만든다.
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
| void merge(vector<int>& v, int s, int e, int m) { vector<int> ret; int i = s, j = m + 1, copy =0;
while(i <= m && j <= e) { if(v[i] < v[j]) ret.push_back(v[i++]); else ret.push_back(v[j++]); }
while(i <= m) ret.push_back(v[i++]); while(j <= e) ret.push_back(v[j++]);
for(int k=s; k<=e; k++) { v[k] = ret[copy++]; } }
void mergeSort(vector<int>& v, int s, int e) { if(s<e) { int m = (s+e)/2; mergeSort(v, s, m); mergeSort(v. m+1, e); merge(v, s, e, m); } }
|
다중 스레드를 사용할 때에는 병합 정렬이 유리합니다. 배열을 나눈 다음 병합할 때 이것은 별도의 스레드에서 수행할 수 있습니다. 하지만 빠른 정렬은 피벗을 사용해 정렬을 수행하므로 스레드간에 문제를 효율적으로 구분하는 것이 어렵습니다.
5. 빠른 정렬(Quick Sort)
- 분할 정복(Divide and conquer)
- 리스트를 피벗(pivot)을 기준으로 작은 값은 왼쪽 큰 값은 오른쪽으로 옮기는 방식으로 정렬한다.
- 분할된 양쪽 리스트에 위 과정을 반복한다.
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
| int partition(vector<int>& v, int left, int right) { int pivot = v[left]; int low = left, high = right+1;
do{ do{ low++ } while(low <= right && v[low] < pivot);
do{ high--; } while( high >= left && v[high] > pivot);
if(low<high) swap(v[low],v[high]);
} while(low < high);
swap(v[left], high);
return high; }
void quickSort(vector<int>& v, int left, int right) { if(left < right) { int pivot_index = partition(v, left, right);
partition(v, left, pivot_index-1); partition(v, pivot_index+1, right); } }
|
정렬된 리스에 대해서 퀵 정렬의 불균형 분할에 의해 오히려 수행시간이 더 많이 걸린다.
분할 과정이 최대 n번 반복될 수 있다.
시간 복잡도 : O(n2)
6. 힙 정렬(Heap Sort)
- 정렬해야 할 n개의 요소들로 최대 힙을 구성한다.
- 힙에서 최소 값을 하나씨 꺼내서 배열의 뒤부터 채운다.
1 2 3 4 5 6 7 8
| #include <algorithm> //힙 사용 void heapSort(vector<int>& v) { std::make_heap(v.begin(), v.end());
for(int i=0; i<v.size(); i++) std::pop_heap(v.begin(), v.end()-i); }
|