기초/알고리즘
[알고리즘] 합병 정렬(Merge Sort)
올리버
2021. 11. 29. 22:50
합병 정렬(Merge Sort)이란?
비교기반 정렬 알고리즘이다.
일반적인 방법으로 구현했을 때 이 정렬은 안정 정렬에 속하며, 분할 정복 알고리즘의 하나이다.
임시 배열 공간을 활용하여 정렬을 수행한다.
장점
- 안정적인 정렬 방법이다.
- 데이터의 분포에 영향을 덜 받는다. 즉, 입력 데이터가 무엇이든 간에 정렬되는 시간은 동일하다. (O(nlog₂n)로 동일)
단점
- 레코드들의 크기가 큰 경우에는 이동 횟수가 많으므로 매우 큰 시간적 낭비를 초래한다.
합병 정렬 과정
1. 리스트의 길이가 0 또는 1이면 이미 정렬된 것으로 본다.
2. 그렇지 않은 경우에는 정렬되지 않은 리스트를 절반으로 잘라 비슷한 크기의 두 부분 리스트로 나눈다.
3. 각 부분 리스트를 재귀적으로 합병 정렬을 이용해 정렬한다.
4. 두 부분 리스트를 다시 하나의 정렬된 리스트로 합병한다.

알고리즘 구현
임시 배열을 사용하는 방식
추가 메모리를 사용하여 정렬을 수행한다.
/**임시 배열을 활용한 합병 정렬*/
int* sorted; //임시 배열
void merge(vector<int>& list, int start,int mid, int end){
int i = start;
int j = mid + 1;
int k = start;
int l;
//병합
while(i <=mid && j <= end){
if(list[i] <= list[j]){
sorted[k++] = list[i++];
}else{
sorted[k++] = list[j++];
}
}
//남아 있는 값 복사
if(i>mid){
for(l = j; l<=end; l++){
sorted[k++] = list[l];
}
}else{
for(l = i; l<=mid; l++){
sorted[k++] = list[l];
}
}
//임시 배열에서 리스트로 이동
for(l = start; l<=end; l++){
list[l] = sorted[l];
}
}
void mergeSort(vector<int>& list, int start, int end){
if(start < end){
int mid = (start + end) / 2;
mergeSort(list, start, mid); //왼쪽 절반 분할 정복
mergeSort(list, mid + 1, end); //우측 절반 분할 정복
merge(list, start, mid, end ); //합병
}
}
int main(){
vector<int> list = {1,29,58,32,2,7,8,100};
sorted = new int[list.size()];
mergeSort(list,0,list.size() -1);
delete sorted;
return 0;
}
연결 리스트(Linked List)를 사용하는 방식
연결 리스트를 사용하게 되면 주소만 가리키게 되기 때문에 메모리 오버플로우가 나지 않는다.
/**Linked List를 활용한 병합 정렬*/
struct Node{
int value;
Node* next = NULL;
};
//리스트를 두개로 분할하는 함수
void splitLeftRight(Node* ref, Node** left, Node** right){
Node* slow = ref;
Node* fast = ref->next;
while(fast != NULL){
fast = fast->next;
if(fast != NULL){
slow = slow->next;
fast = fast->next;
}
}
*left = ref;
*right = slow->next; //mid + 1 과 동일
slow->next = NULL; // NULL로 잡아줘야 두개로 나뉘게 된다.
}
//합병을 진행하는 함수
Node* merge(Node* left, Node* right){
Node* merged = NULL;
if(left == NULL) return right;
if(right == NULL) return left;
if(left->value <= right->value){
merged = left;
merged->next = merge(left->next,right);
}else{
merged = right;
merged->next = merge(left,right->next);
}
return merged;
}
void mergeSort(Node** ref){
Node* head = *ref;
Node* left;
Node* right;
if(head == NULL || head->next ==NULL){
return;
}
//분할
splitLeftRight(head, &left, &right);
//왼측 분할
mergeSort(&left);
//우측 분할
mergeSort(&right);
//왼측, 우측 합병
*ref = merge(left,right);
}
void addValue(Node** head, int value){
Node* newNode = new Node();
newNode->value = value;
//헤드의 주소에 해당 하는 것을 next로 설정
newNode->next = *head;
//헤드를 새로운 노드의 주소로 변경
*head = newNode;
}
int main(){
vector<int> list = {27, 10, 12, 20, 25, 13, 15, 22};
Node* linkedList = new Node();
for(int i =0; i< list.size(); i++){
addValue(&linkedList,list[i]);
}
mergeSort(&linkedList);
return 0;
}
시간 복잡도
- 완전히 분할하는데 log(n) 레벨을 생성한다.
- 합병하는데 할당된 리스트의 길이 만큼, 즉 N번 반복한다.
- 평균 시간복잡도는 O(n * log2 n)