정렬 알고리즘
< 병합 정렬(Merge sort) >
-
‘존 폰 노이만(John von Neumann)’이라는 사람이 제안한 방법
-
일반적인 방법으로 구현했을 때 이 정렬은 안정 정렬 에 속하며, 분할 정복 알고리즘의 하나 이다.
-
분할 정복(divide and conquer) 방법
-
문제를 작은 2개의 문제로 분리하고 각각을 해결한 다음, 결과를 모아서 원래의 문제를 해결하는 전략이다.
-
분할 정복 방법은 대개 순환 호출을 이용하여 구현한다.
-
-
과정 설명
-
리스트의 길이가 0 또는 1이면 이미 정렬된 것으로 본다.
-
그렇지 않은 경우에는정렬되지 않은 리스트를 절반으로 잘라 비슷한 크기의 두 부분 리스트로 나눈다.
-
각 부분 리스트를 재귀적으로 합병 정렬을 이용해 정렬한다.
-
두 부분 리스트를 다시 하나의 정렬된 리스트로 합병한다.
-
로직<오름차순>오름차순>
- 현재 배열을 반으로 쪼갠다. 배열의 시작위치와 종료위치를 더한 값에 2 나누어 기준점을 찾는다.
- 계속 쪼개서 배열의 크기가 0또는 1일때까지 반복한다.
- 두 배열 A,B를 비교한다. 각각의 인덱스를 i,j라고 가정했을때, i=A의 시작 인덱스, j=B의 시작인덱스
- A[i]와 B[j]를 비교 이 중 작은 값을 새로운 배열 C에 삽입한다.
- 그리고 그 작은 값을 가지고 있던 배열 의 인덱스를 +1 해준다.
- 즉, A[i]가 더 작았다면 다음 비교는 A[i+1] 과 B[j] 이다.
- 이 과정을 한 배열이 끝까지 도달할떄까지 반복한다.
- 그러면 나머지 배열의 남은 값들을 배열 C에 차례대로 넣어준다.
- C를 원래 배열에 저장한다.
병합정렬의 시간복잡도는 두 배열 A(n/2) B(n/2) 를 정렬하기때문에 O(n/2+n/2) -> O(n)이라고 할 수 있다. 분할과정은 lgN만큼 일어나는데, N크기의 배열을 분할하면 N/2 N/2로 2개 -> N/4 N/4 N/4 N/4 로 4개…,즉,분할 과정은 매번 반씩 감소하므로 lgN 만큼 반복해야 크기가 1인 배열로 분할 할 수 있다.
각 분할별로 병합을 진행하므로 전체의 시간복잡도는 O(NlgN) 이다.
장점
- 안정적인 정렬 방법
- 데이터의 분포에 영향을 덜 받는다. 즉, 입력 데이터가 무엇이든 간에 정렬되는 시간은 동일하다.
- 만약 레코드를 연결 리스트(Linked List)로 구성하면, 링크 인덱스만 변경되므로 데이터의 이동은 무시할 수 있을 정도로 작아진다.
- 제자리 정렬(in-place sorting)로 구현할 수 있다.
- 따라서 크기가 큰 레코드를 정렬할 경우에 연결 리스트를 사용한다면, 합병 정렬은 퀵 정렬을 포함한 다른 어떤 졍렬 방법보다 효율적이다.
단점
- 만약 레코드를 배열(Array)로 구성하면, 임시 배열이 필요하다.
- 제자리 정렬(in-place sorting)이 아니다.
- 레크드들의 크기가 큰 경우에는 이동 횟수가 많으므로 매우 큰 시간적 낭비를 초래한다.
코드
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
#include <iostream>
#include <vector>
using namespace std;
vector<int> v{21,10,12,20,25,13,15,22};
void Merge(int s,int e,int m){
vector<int> temp(8);
int i=s , j=m+1, idx=s;// idx:새로운 배열의 인덱스
while(i<=m &&j <=e){
if(v[i]<=v[j])
temp[idx++]=v[i++];
else
temp[idx++]=v[j++];
}
if(i>m){
for(int t=j;t<=e;t++)
temp[idx++]=v[t];
}
else{
for(int t=i;t<=m;t++)
temp[idx++]=v[t];
}
for(int t=s;t<=e;t++)
v[t]=temp[t];
}
void MergeSort(int s,int e){//start,end
if(s>=e) return;
int m = (s+e)/2;
MergeSort(s,m);
MergeSort(m+1,e);
Merge(s,e,m);
}
int main(){
MergeSort(0,7);
for(int i=0;i<8;i++)
cout << v[i] << ' ';
}
< 퀵 정렬(Quick sort) >
- ‘찰스 앤터니 리처드 호어(Charles Antony Richard Hoare)’가 개발한 정렬 알고리즘
- 퀵 정렬은 불안정 정렬 에 속하며, 다른 원소와의 비교만으로 정렬을 수행하는 비교 정렬 에 속한다.
- 분할 정복 알고리즘의 하나로, 평균적으로 매우 빠른 수행 속도를 자랑하는 정렬 방법
- 합병 정렬(merge sort)과 달리 퀵 정렬은 리스트를 비균등하게 분할한다
- 과정 설명
- 리스트 안에 있는 한 요소를 선택한다. 이렇게 고른 원소를 피벗(pivot) 이라고 한다.
- 피벗을 기준으로 피벗보다 작은 요소들은 모두 피벗의 왼쪽으로 옮겨지고 피벗보다 큰 요소들은 모두 피벗의 오른쪽으로 옮겨진다. (왼쪽원소 < 피벗 < 오른쪽원소 )
- 피벗을 제외한 왼쪽 리스트와 오른쪽 리스트를 다시 정렬한다.
- 분할된 부분 리스트에 대하여 순환 호출 을 이용하여 정렬을 반복한다.
- 부분 리스트에서도 다시 피벗을 정하고 피벗을 기준으로 2개의 부분 리스트로 나누는 과정을 반복한다.
- 부분 리스트들이 더 이상 분할이 불가능할 때까지 반복한다.
- 리스트의 크기가 0이나 1이 될 때까지 반복한다.
로직
- 피벗으로 잡을 값을 정한다.(맨 앞, 맨 뒤, 중간값, 랜덤값 중 선택)
- 배열의 맨 왼쪽원소를 left, 오른쪽원소를 right로 두고
- right 원소를 피벗과 비교하며 피벗보다 클 경우 right-1하고 다시 탐색 ,피벗보다 작을 경우 탐색을 멈춘다.
- left 원소를 피벗과 비교하며 피벗보다 작을 경우 left +1하고 다시 탐색, 피벗보다 클 경우 탐색을 멈춘다.
- left 원소와 right 원소를 스왑해준다.
- 위의 3,4,5과정을 left>right가 될때까지 반복해준다.
- 위 과정이 끝나면 피벗과 left를 바꿔준다.
- 다시 0~left-1 , left~끝까지로 나눠 정렬 시작.
각 배열의 정렬은 크기 N만큼 비교하고, 분할과정은 lgN만큼 진행하므로 시간복잡도는 O(lgN) 하지만 최악의 경우(모든 원소가 다 정렬 되어있는 경우) 에는 O(n^2)의 시간복잡도를 가지지만 거의 이런 경우는 없다.
장점
- 속도가 빠르다.
- 시간 복잡도가 O(nlog₂n)를 가지는 다른 정렬 알고리즘과 비교했을 때도 가장 빠르다.
- 추가 메모리 공간을 필요로 하지 않는다.
- 퀵 정렬은 O(log n)만큼의 메모리를 필요로 한다.
단점
-
정렬된 리스트에 대해서는 퀵 정렬의 불균형 분할에 의해 오히려 수행시간이 더 많이 걸린다.
-
퀵 정렬의 불균형 분할을 방지하기 위하여 피벗을 선택할 때 더욱 리스트를 균등하게 분할할 수 있는 데이터를 선택한다.
EX) 리스트 내의 몇 개의 데이터 중에서 크기순으로 중간 값(medium)을 피벗으로 선택한다
코드
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
//3.퀵 정렬
#include <iostream>
#include <vector>
using namespace std;
vector<int> v{21,10,12,20,25,13,15,22};
void QuickSort(int s,int e){
if(s>=e) return;
int pivot=v[s];
int left=s+1,right=e;
while(left<=right){//엇갈릴때까지
while(v[left]<pivot && left<=e)
left++;
while(v[right]>pivot && s+1<=right)
right--;
if(left>right){//엇갈림
swap(v[s],v[right]);
}
else{
swap(v[left],v[right]);
}
}//while
QuickSort(s,right-1);
QuickSort(right+1,e);
}
int main(){
QuickSort(0,7);
for(int i=0;i<8;i++)
cout << v[i] << ' ';
}
참고
https://hsp1116.tistory.com/33
https://gmlwjd9405.github.io/2018/05/10/algorithm-quick-sort.html
https://hongku.tistory.com/149