May 31, 2020
합병정렬은 퀵정렬과 마찬가지로 분할정복을 이용해서 정렬을 수행하는 알고리즘이다. 퀵정렬과는 다르게 항상 𝛩(nlgn) 의 시간 복잡도를 보장한다는 장점이 있지만, 정렬을 위해 추가적인 메모리를 크게 사용한다는 단점이 있다.
합병정렬은 다음과 같은 과정을 통해 배열의 요소들을 정렬하게 된다. (오름차순)
말로만 하면 헷갈리니까 아래 배열을 머지소트로 정렬하는 과정을 따라가보자.
계속 진행하다보면 마지막으로 합쳐주는 구간이 나오는데, 합쳐주는 과정을 더 상세하게 보기 위해 이 구간에서 합치는 과정을 한단계씩 자세히 확인해보자. 일단 두 배열의 길이의 합인 6만큼의 새로운 배열을 만들었다.
두 배열의 시작 값인 2와 1을 비교해서 더 작은 값을 새 배열에 넣어준다.
1은 이미 사용된 값이니까 검사 값을 배열의 다음값으로 바꿔준다.
위와 마찬가지로 2 와 6을 비교하고 더 작은 값인 2를 새 배열에 넣은 뒤, 배열의 참조위치를 한칸 이동시킨다.
3과 6중 더 작은 값인 3을 배열에 넣고 참조값을 한칸 뒤로 이동시킨다.
5와 6 중 더 작은 값인 5를 배열에 넣는다. 이번에는 5가 마지막 값이기 때문에 그냥 끝낸다.
왼쪽 배열의 모든 요소에 대한 처리가 끝났기 때문에 오른쪽 배열에서 처리가 안된 값들을 순차적으로 새 배열에 채워준다. 이미 오른쪽 배열은 병합하는 과정에서 정렬이 되어있기 때문에 순서대로 넣어주기만 해도 정렬은 유지된다.
#include <stdio.h>
void merge(int arr[], int left, int mid, int right){
int cur_left = left; // 왼쪽 배열요소를 검사할 커서
int cur_right = mid+1; // 오른쪽 배열요소를 검사할 커서
int i = left; // 암사배열의 시작위치. 항상 0이 아님.
int temp_arr[10];
while (cur_left <= mid && cur_right <= right){ // 왼쪽이나 오른쪽 커서가 끝에 도달할때까지 반복
if (arr[cur_left] <= arr[cur_right]){ // 왼쪽 커서가 가르키는 값이 오른쪽 커서가 가르키는 값보다 작으면
temp_arr[i] = arr[cur_left]; // 해당값을 임시배열에 넣기
cur_left++; // 커서 한칸 이동
}
else{
temp_arr[i] = arr[cur_right]; // 오른쪽 커서가 참조하는 값이 더 큰 경우에는 오른쪽 값을 임시 배열로
cur_right++;
}
i++; // 임시배열의 참조위치 증가
}
if (cur_left > mid){ // 왼쪽 배열의 커서가 mid 보다 크다면 적어도 왼쪽 배열 요소들은 모두 사용됨.
for (int j = cur_right ; j <= right ; j++){ // 혹시 오른쪽 배열에 있는 값들 중 사용이 안된 값이 있을 수도 있으니까 오른쪽 커서를 쭉 돌리면서 체크
temp_arr[i] = arr[j]; // 남은 값들로 배열 채워주기
i++;
}
}
else{
for (int j = cur_left ; j <= mid ; j++){
temp_arr[i] = arr[j];
i++;
}
}
for (int j = 0 ; j <= right ; j++){
a[j] = temp_arr[j]; // 임시 배열의 값을 기존 배열에 복사
}
}
void merge_sort(int arr[], int left, int right){
int mid;
if(left < right){ // 배열의 길이가 1이 아니라면 계속 재귀호출로 분할
mid = (left + right)/2;
merge_sort(arr, left, mid); // 왼쪽 배열 정렬
merge_sort(arr, mid+1, right); // 오른쪽 배열 정렬
}
}머지소트의 시간 복잡도는 배열을 계속해서 반으로 분리하기 때문의 완전이진트리의 높이인 lgn 과 비교연산을 통해 새로운 배열을 채우는 과정이 모든 단계마다 수행되기 때문에 𝛩(nlgn) 의 복잡도를 가진다.