Search

합병정렬

Merge Sort(합병정렬)

합병 정렬 또는 병합 정렬은 O(n log n) 비교 기반 정렬 알고리즘이다. 일반적인 방법으로 구현했을 때 이 정렬은 안정 정렬에 속하며, 분할 정복 알고리즘의 하나이다. 존 폰 노이만이 1945년에 개발했다. 위키백과
안정 정렬
분할 정복 알고리즘
분할 정복 알고리즘(divide and conquer)은
문제를 작은 2개의 문제로 분리해 각각 해결한 후, 결과를 모아 원래의 문제를 해결하는 전략
보통 순환 호출을 통해 구현
1.
리스트의 길이가 0또는 1이면 이미 정렬된 것으로 봄
2.
리스트 길이가 2 이상이라면 리스트를 절반으로 나누어 비슷한 크기의 두 부분리스트로 나눔
3.
각 부분 리스트를 재귀적으로 합병정렬로 정렬
4.
두 부분 리스트를 다시 하나의 정렬된 리스트로 합병

합병정렬 알고리즘

분할
정복
결합
이 세가지 단계로 이뤄짐
ex1)
[21, 10, 12, 20, 25, 13, 15, 22] 배열이 있다고 할 때,
1.
2개의 리스트의 값들을 처음부터 하나씩 비교하여 두 개의 리스트의 값 중에서 더 작은 값을 새로운 리스트(sorted)로 옮긴다.
2.
둘 중에서 하나가 끝날 때까지 이 과정을 되풀이한다.
3.
만약 둘 중에서 하나의 리스트가 먼저 끝나게 되면 나머지 리스트의 값들을 전부 새로운 리스트(sorted)로 복사한다.
4.
새로운 리스트(sorted)를 원래의 리스트(list)로 옮긴다.
ex2)
파란 동그라미가 순서를 나타냄
그림처럼 부분리스트 크기가 1이 될 때 까지 분할합니다(단계 5까지)
이후 합병하며 정렬을 하게되는데, 순서가 중요합니다
합병의 순서는 분할의 역순이라고 할 수 있겠습니다.
그림에서 보면 5단계의 맨 앞에 보이는 30, 20이 가장 마지막에 분할되었으므로 맨 처음으로 합병해줍니다.
합병을 하는거는 합병 대상인 두 리스트의 원소를 앞에서부터 비교하여 작은 값을 갖는 원소를 새로운 리스트에 넣어주는 방식으로 생각하면 됩니다.

합병정렬 구현

void MergeSort(int A[ ], int Left, int Right) /* 입력: A[Left:Right], Left, Right 출력: A[Left:Right] - 정렬된 배열. */ { int Mid; if (Left < Right) { Mid = (Left + Right)/2; MergeSort(A[ ], Left, Mid); MergeSort(A[ ], Mid+1, Right); Merge(A[ ], Left, Mid, Right); } } void Merge(int A[ ], int Left, int Mid, int Right) /* 입력: A[Left:Mid], A[Mid+1:Right] - 정렬된 두 배열. 출력: A[Left:Right] : A[Left:Mid]와 A[Mid+1:Right] 를 합병 */ { int B[NUM_OF_KEYS], i, LeftPtr, RightPtr, BufPtr; LeftPtr = Left; RightPtr = Mid + 1; BufPtr = Left; // 분할 정렬된 list 합병 while (LeftPtr <= Mid && RightPtr <= Right){ if (A[LeftPtr] < A[RightPtr]) B[BufPtr++] = A[LeftPtr++]; else B[BufPtr++] = A[RightPtr++]; } // 남은 값 일괄 복사 if (LeftPtr > Mid) { for (i = RightPtr; i <= Right; i++) B[BufPtr++] = A[i]; } // 남은 값 일괄 복사 else{ for (i = LeftPtr; i <= Mid; i++) B[BufPtr++] = A[i]; } // 임시배열 B의 리스트를 원래 배열 A로 재복사 for (i = Left; i <= Right; i++) A[i] = B[i]; }
C
복사
void main(){ int i; int n = 8; int list[n] = {21, 10, 12, 20, 25, 13, 15, 22}; // 합병 정렬 수행(left: 배열의 시작 = 0, right: 배열의 끝 = 7) merge_sort(list, 0, n-1); } }
C
복사
단점
만약 레코드를 배열(Array)로 구성하면, 임시 배열이 필요하다.(입력 크기에 비례하는)
제자리 정렬(in-place sorting)이 아니다.
레크드들의 크기가 큰 경우에는 이동 횟수가 많으므로 매우 큰 시간적 낭비를 초래한다.
장점
안정적인 정렬 방법 (순차적으로 이동하기 때문에)
데이터의 분포에 영향을 덜 받는다. 즉, 입력 데이터가 무엇이든 간에 정렬되는 시간은 동일하다. (O(nlog2(n))O(n\log_2(n))로 동일)
만약 레코드를 연결 리스트(Linked List)로 구성하면, 링크 인덱스만 변경되므로 데이터의 이동은 무시할 수 있을 정도로 작아진다.
⇒ 제자리 정렬(in-place sorting)로 구현할 수 있다.
따라서 크기가 큰 레코드를 정렬할 경우에 연결 리스트를 사용한다면, 합병 정렬은 퀵 정렬을 포함한 다른 어떤 졍렬 방법보다 효율적이다.
[시간복잡도]

Merge함수

최선시간복잡도 = min(m, n) :
이 때 왼쪽 배열의 모든 원소가 오른쪽 배열의 모든 원소보다 작기 때문에
왼쪽 배열 크기 m, 오른쪽 배열 크기 n일 경우 오른쪽 배열의 첫번째 원소가 왼쪽 배열의 모든 원소와 한번씩 비교가 이뤄진 뒤 더 이상의 비교는 진행되지 않고 복사하는 과정만 이뤄지기 때문이다.
ex) 2, 3, 4, 0, 1 | 7, 8, 9, 5, 6
최악시간복잡도 = m+n-1
ex) m=n일때,
a1, a2, ..., am, b1, b2, ..., .bn에 대해
정렬순서가 a1,b1, a2, b2, ..., am, bm인 경우
ex) 0 8 4 2 6 | 1 9 5 3 7

MergeSort함수

별도의 배열 B를 사용하므로 제자리 정렬이 아니다(Merge함수에서 입력크기에 비례하는 메모리를 사용하기 때문)
안정성 O
최악시간복잡도 = O(Nlog(N))O(N\log(N))
ex) 0 8 4 2 6 1 9 5 3 7
N개의 데이터가 주어져있다면
절반-절반-절반 해가다가 1의 크기 갖는 리스트로 분할때까지 즉, log2(N)\log_2(N)의 깊이가 발생하고 합병은 역순을 따르기 때문에 그 또한 log2(N)\log_2(N)의 깊이를 갖게되기 때문. (두번의 log2(N)\log_2(N)과정)