이번에는 병합 정렬 알고리즘을 살펴봅시다.
병합 정렬 알고리즘은 배열을 작은 단위의 배열로 분할한 후에 분할한 배열을 정렬하고 이들을 다시 정렬하면서 전체 배열을 정렬하는 알고리즘입니다.
알고리즘
병합 정렬(base:배열의 시작 주소, n: 원소 개수, compare:비교 논리)
ah:= n/2
bh:= n – ah;
조건(n이 1보다 작거나 같으면) 종료
병합정렬(base,ah,compare)
병합접열(base+ah,bh,compare)
tbase에 동적 메모리 할당(원소크기*원소개수)
메모리 복사(tbase,base)
ai:=0
bi:=ah
i:=0
반복(ai가 ah보다 작으면서 bi가 n보다 작다)
조건(tbase[ai]가 tbase[bi]보다 작거나 같으면
base[i] := base[ai]
ai:= ai+1
아니면
base[i]:= base[bi]
bi:= bi+1
i:=i+1
반복(ai가 ah보다 작다)
base[i]:= tbase[ai]
i:=i+1
ai:=ai+1
반복(bi가 n보다 작다)
base[i]:= tbase[bi]
i:=i+1
bi:=bi+1
점근식 계산
병합 정렬에서 분할 과정은 n개일 때 1, n/2일 때 두 번(분할한 두 개가 각각 한 번 분할),…해서 총 n-1번 분할합니다.
분할 횟수 = 1+2+4+8+…+n/2 = n-1
정복 과정에서는 분할 횟수가 h이고 분할한 h개를 h/2개로 병합하기 위해 비교 회수는 최악일 때 n보다 작습니다. 분할 횟수는 logN이므로 정복에 들어가는 전체 비용은 NlogN보다 작습니다.
정복 과정에서의 비교 횟수<=NlogN
따라서 병합 정렬의 전체 비용은 최악일 때 O(NlogN)이라고 말할 수 있습니다. 앞에서 살펴본 힙 정렬과 마찬가지로 병합 정렬은 O(NlogN)의 성능을 보여줍니다. 특히 언제나 반 씩 분할한 후에 정복하기 때문에 자료의 배치 상태에 관계없이 일정한 성능을 보여주는 정렬 방식입니다.
그리고 같은 값을 갖고 있을 때 원래 앞에 있는 원소가 정렬 후에도 앞에 있게 정렬하는 안정적인 정렬 알고리즘입니다. 만약 정렬에서 우선적으로 정렬할 키와 차선으로 정렬할 키가 있을 때 차선의 키로 정렬한 후에 우선적인 키로 안정적인 정렬을 수행하면 원하는 결과를 얻을 수 있습니다.
두 개의 키로 정렬하기: 차선의 키로 정렬 → 우선적인 키로 안정적인 정렬
소스 코드
//https://ehpub.co.kr
//[언제나 C언어] 병합 정렬(Merge Sort) [예제 Center]
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#define SWAP(a,b) {int t; t=a; a=b; b=t;}
void MergeSort(int* base, int n);
void ViewArr(int* base, int n);
int main()
{
int arr[10] = { 9,4,3,10,5,8,7,6,2,1 };
ViewArr(arr, 10);
MergeSort(arr, 10);
ViewArr(arr, 10);
return 0;
}
void MergeSort(int* base, int n)
{
int ahalf = n / 2;
int bhalf = n - ahalf;
int ai = 0, bi = ahalf;
int i = 0;
int* tbase = 0;//static int tbase[100000];
if (n <= 1)
{
return;
}
MergeSort(base, ahalf);
MergeSort(base + ahalf, bhalf);
tbase = (int*)malloc(sizeof(int) * n);
memcpy(tbase, base, sizeof(int) * n);
while ((ai < ahalf) && (bi < n))
{
if (tbase[ai] <= tbase[bi])
{
base[i] = tbase[ai];
ai++;
}
else
{
base[i] = tbase[bi];
bi++;
}
i++;
}
while (ai < ahalf)
{
base[i] = tbase[ai];
i++;
ai++;
}
while (bi < n)
{
base[i] = tbase[bi];
i++;
bi++;
}
free(tbase);
}
void ViewArr(int* base, int n)
{
int i = 0;
for (i = 0; i < n; i++)
{
printf("%d ", base[i]);
}
printf("\n");
}
시뮬레이션 용 코드
#include <stdlib.h>
#include <string.h>
#include <conio.h>
#include <stdio.h>
#pragma warning(disable:4996)
#pragma warning(disable:4244)
#define SWAP(a,b) {int t; t = a; a=b; b=t;}//a와 b를 교환
int* origin;
int on;
void MergeSort2(int* base, int n);
void MergeSort(int* base, int n);
void ViewArr(int* arr, int n);
int main(void)
{
int arr[10] = { 9,4,3,10,5,8,7,6,2,1 };
origin = arr;
on = 10;
ViewArr(origin, on);
MergeSort2(arr, 10);
getch();
ViewArr(origin, on);
system("pause");
return 0;
}
void MergeSort(int* base, int n)
{
int ahalf = n / 2; //배열의 앞쪽 개수
int bhalf = n - ahalf; //배열의 뒤쪽 개수
int ai = 0, bi = ahalf;
int i = 0;
int* tbase = 0;
if (n <= 1)//배열의 크기가 1보다 작거나 같을 때
{
return;
}
MergeSort(base, ahalf);//앞쪽 배열 재귀호출로 정렬
MergeSort(base + ahalf, bhalf);//뒤쪽 배열 재귀호출로 정렬
tbase = (int*)malloc(sizeof(int) * n);//배열 크기의 임시 공간을 할당
memcpy(tbase, base, sizeof(int) * n);//임시 공간에 배열 메모리 복사
while ((ai < ahalf) && (bi < n))
{
if (tbase[ai] <= tbase[bi])//뒤쪽이 크거나 같을 때
{
base[i] = tbase[ai];//앞쪽 배열의 원소를 대입
ai++;
}
else
{
base[i] = tbase[bi];//뒤쪽 배열의 원소를 대입
bi++;
}
i++;
}
while (ai < ahalf)//앞쪽 배열의 남은 것들을 대입
{
base[i] = tbase[ai];
i++;
ai++;
}
while (bi < n)//뒤쪽 배열의 남은 것들을 대입
{
base[i] = tbase[bi];
i++;
bi++;
}
free(tbase);//임시 버퍼에 할당한 메모리 해제
}
void PrintSpace(int n);
void MergeSort2(int* base, int n)
{
int ahalf = n / 2; //배열의 앞쪽 개수
int bhalf = n - ahalf; //배열의 뒤쪽 개수
int ai = 0, bi = ahalf;
int i = 0;
int* tbase = 0;
if (n <= 1)//배열의 크기가 1보다 작거나 같을 때
{
return;
}
MergeSort2(base, ahalf);//앞쪽 배열 재귀호출로 정렬
PrintSpace(base - origin);
ViewArr(base, ahalf);
MergeSort2(base + ahalf, bhalf);//뒤쪽 배열 재귀호출로 정렬
PrintSpace(base + ahalf - origin);
ViewArr(base + ahalf, bhalf);
tbase = (int*)malloc(sizeof(int) * n);//배열 크기의 임시 공간을 할당
memcpy(tbase, base, sizeof(int) * n);//임시 공간에 배열 메모리 복사
while ((ai < ahalf) && (bi < n))
{
if (tbase[ai] <= tbase[bi])//뒤쪽이 크거나 같을 때
{
base[i] = tbase[ai];//앞쪽 배열의 원소를 대입
ai++;
}
else
{
base[i] = tbase[bi];//뒤쪽 배열의 원소를 대입
bi++;
}
i++;
}
while (ai < ahalf)//앞쪽 배열의 남은 것들을 대입
{
base[i] = tbase[ai];
i++;
ai++;
}
while (bi < n)//뒤쪽 배열의 남은 것들을 대입
{
base[i] = tbase[bi];
i++;
bi++;
}
free(tbase);//임시 버퍼에 할당한 메모리 해제
}
void PrintSpace(int n)
{
int i = 0;
getch();
for (i = 0; i < n; i++)
{
printf(" ");
}
}
void ViewArr(int* arr, int n)
{
int i = 0;
for (i = 0; i < n; i++)
{
printf("%2d ", arr[i]);
}
printf("\n");
}