Today's special moments become memories of tomorrow.

Algorithm & DS/정렬

병합 정렬(Merge Sort)

lotus lee 2021. 2. 20. 15:45

병합 정렬(Merge Sort)

배열을 두 개로 쪼개어서 정렬을 한 뒤, 다시 병합하는 작업을 반복하는 정렬 알고리즘

 

배열을 중앙을 기준으로 앞(left ~ center)과 뒤(center+1 ~ right)로 나눈다. 이 작업을 배열의 요소가 2개가 될 때까지 반복한다. 

나누어진 두 개의 배열을 정렬한 뒤 하나의 배열로 병합한다. 이렇게 배열을 모두 정렬 및 병합하면 종료한다.

 

 

 

 

시간복잡도 : O(NlogN)

퀵 정렬은 최악의 경우 시작복잡도가 O(N²) 이지만, 병합 정렬은 O(NlogN)로 유지된다.

 

 

[예제]

배열 arr의 요소가 5 9 3 1 8 6 4 7 2 일 때, 이 배열을 오름차순으로 정렬하기

 

 

먼저 배열 arr의 left = 0, right = arr.length -1 로 초기화해준다.

left는 배열의 맨 왼쪽 끝 인덱스이고, right는 배열의 맨 오른쪽 끝 인덱스이다.

그리고 center = (left + right) / 2 가 되어 center가 배열의 중앙 인덱스를 담도록 한다.

 

< 배열 나누기>

병합 정렬은 먼저 배열을 앞 뒤로 쪼개는 작업을 해야하는데, 이 작업은 재귀를 이용하여 구현할 수 있다.

mergeSort(arr, left, center);

mergeSort(arr, center+1, right);

 

mergeSort()의 첫번째 인수는 정렬할 배열이고, 두번째 인수는 쪼개진 배열의 맨 왼쪽 인덱스, 

세번째 인수는 쪼개진 배열의 맨 오른쪽 인덱스가 들어간다.

 

이렇게 앞과 뒤로 나누어진 배열은 또 다시 재귀를 반복하며 left ~ center, center+1 ~ right 로 쪼갠다.

재귀를 사용할 때는 베이스 케이스가 있어야 한다.

병합 정렬에서는 배열을 더 이상 쪼갤 수 없을 때가 베이스 케이스가 될 것이다.

즉, left < right 조건을 만족할 때까지 배열을 쪼갤 수 있을 것이다.

left = right 이면 쪼개진 배열의 요소가 한 개밖에 없기 때문에 더 이상 배열을 쪼갤 수 없다. 그러므로 재귀를 종료한다.

= 배열의 divide를 종료한다.

< 정렬 및 병합하기>

이제 나누어진 배열을 정렬 -> 병합을 할 차례이다.먼저 left = 0 ,right = 1 인 범위부터 정렬한다.

버퍼를 하나 생성한다. 버퍼의 크기는 쪼개어진 앞쪽 배열의 크기(center - left + 1)만큼 할당한다. 

int[] buf = new int[center - left + 1];

위의 그림에서 앞쪽 배열의 크기는 1이다. 그러므로 버퍼의 크기는 1이 된다.

그 다음, 앞쪽의 배열의 요소를 모두 버퍼에 담는다.

pl, pr 변수를 만들어서 pl은 버퍼의 가장 왼쪽을 가리키고, pl = 0

pr은 뒤쪽 배열의 가장 왼쪽을 가리키도록 한다. pr = center+1

(뒤쪽 배열의 가장 첫번째 인덱스는 center+1 이었다.)

 

그리고 idx 변수를 생성하여, 현재 배열 arr에서 정렬을 하려고하는 범위의 첫번째 위치를 담는다.

현재 정렬을 하려고하는 범위는 left = 0 ~ right = 1 이므로 idx = 0 이 들어간다.

 

이제부터 버퍼의 pl이 가리키는 값arr의 pr이 가리키는 값의 대소관계를 비교하여 더 작은 값이 arr의 left부터 차례대로 담길 것이다. 

 

pl이 가리키는 값이 더 작으면 arr[idx] = buf[pl] 이 들어가고, pl은 버퍼의 다음 위치로 이동한다.

pr이 가리키는 값이 더 작으면 arr[idx] = arr[pr] 이 들어가고, pr은 arr의 다음 위치로 이동한다.

 

pl이 가리키는 값 < pr이 가리키는 값   -> arr[idx++] = buf[pl++];  

pl이 가리키는 값 > pr이 가리키는 값   -> arr[idx++] = buf[pr++]; 

 

이 과정을 pl이 버퍼의 범위를 벗어나거나, pr이 범위를 벗어날 때까지 반복한다.

 

여기서는 buf[pl] < arr[pr] 이므로 arr[0] = buf[pl] = 5 가 들어간다. 그리고 pl은 하나 증가하여 1이 된다.

pl 이 버퍼의 범위를 벗어났으므로 반복문이 종료된다.

 

 

 

반복문이 종료된 후에도 pl이 아직 버퍼의 범위 안에 있는 경우(pl < buf.length)에는

pl을 끝까지 이동시키며 버퍼의 요소를 arr에 차례대로 담는다.

여기서는 pl이 이미 버퍼의 범위를 벗어났기 때문에 더 이상 작업을 진행하지 않고 정렬을 종료한다.

 

 

 

이제, left = 0, right = 2 인 범위를 정렬해보자.

 

 

마찬가지로 배열을 쪼개었을 때, 앞쪽에 해당하는 배열의 요소를 버퍼에 옮겨담는다.

 

pl = 0, pr = 2, idx = 0 으로 초기화한다.

그리고 buf[pl]과 arr[pr]을 비교하면서 더 작은 값을 arr[idx]에 넣는 작업을 반복한다.

 

buf[0] > arr[pr] 이므로 arr[0] 에는 arr[pr] = 3 이 들어간다. 그리고 pr은 하나 증가하여 3이 된다.

pr이 right = 2보다 크므로 범위를 벗어났으므로 바로 반복문이 종료된다.

 

반복문이 종료된 후, pl은 여전히 버퍼의 범위 안에 있다. pl < buf.length 

그러므로 pl을 이동시키면서 버퍼의 값들을 arr[1]부터 시작하여 차례대로 옮겨담는다.

 

버퍼의 값들을 모두 옮겨담으면, left = 0 부터 right = 2 까지 범위의 정렬 및 병합이 완료된다.

 

나머지 과정도 위와 동일하다.

left = 0 ~ right = 4 범위의 정렬 & 병합이 완료된 상태이고,

left = 5 ~ right = 8 범위의 정렬 & 병합이 완료된 상태라고 가정하고(과정은 위와 동일하므로 생략한다)

 

 

이 두 배열을 정렬 및 병합하는 과정을 마지막으로 보자.

 

 

left = 0, right = 8 인 범위(배열 전체)를 정렬 및 병합할 차례이다.

이 때, 앞쪽의 배열(초록색부분), 뒤쪽의 배열(파란색부분)은 각각 정렬을 마친 상태일 것이다.

 

버퍼를 생성하고 앞쪽의 배열 값들을 버퍼에 옮겨담는다.

 

pl = 0(버퍼의 맨 왼쪽 인덱스), pr = 5(뒤쪽 배열의 맨 왼쪽 인덱스 : center + 1), idx = 0 으로 초기화한다.

buf[0] < arr[5] 이므로 arr[0] = buf[0] = 1 이 들어간다. 그리고 pl이 하나 증가한다.

buf[1] > arr[5] 이므로 arr[1] = arr[5] = 2 가 들어가고, pr이 하나 증가한다.

 

buf[1] < arr[6] 이므로 arr[2] = buf[1] = 3 이 들어가고, pl이 하나 증가하여 2가 된다.

 

buf[2] > arr[6] 이므로 arr[3] = arr[6] = 4 가 들어가고, pr이 하나 증가하여 7이 된다.

 buf[2] < arr[7] 이므로 arr[4] = buf[2] = 5 가 들어가고, pl이 하나 증가한다.

 

buf[3] > arr[7] 이므로 arr[5] = arr[7] = 6이 들어가고, pr이 하나 증가한다.

buf[3] > arr[8] 이므로 arr[6] = arr[8] = 7 이 들어가고, pr은 하나 증가하여 9가 된다.

여기서 pr이 범위를 벗어났으므로 반복문이 종료된다.

 

반복문이 종료된 후, pl을 보니 아직 버퍼의 범위 안에 있다. pl < buf.length 

그러므로 버퍼의 나머지 값들을 arr[7]부터 차례대로 옮겨담는다.

 

최종적으로 배열 arr의 정렬이 완료된 것을 확인할 수 있다.

 

 

소스코드 :

	public static void mergeSort(int[] arr, int left, int right) {

		if (left < right) {

			int center = (left + right) / 2;

			// 배열 쪼개기
			mergeSort(arr, left, center);
			mergeSort(arr, center + 1, right);

			// 정렬 및 병합
			int size = center - left + 1;
			int[] buf = new int[size]; // 버퍼 생성

			for (int i = left; i <= center; i++)
				buf[i - left] = arr[i];

			int pl = 0, pr = center + 1;
			int idx = left;

			while (pl < size && pr <= right)
				arr[idx++] = (buf[pl] < arr[pr]) ? buf[pl++] : arr[pr++];

			while (pl < size)
				arr[idx++] = buf[pl++];
		}
	}

'Algorithm & DS > 정렬' 카테고리의 다른 글

힙 정렬(Heap Sort)  (0) 2021.02.22
퀵 정렬(Quick Sort)  (2) 2021.02.17
단순 삽입 정렬(Straight Insertion Sort)  (0) 2021.02.16
단순 선택 정렬(Straight Selection Sort)  (0) 2021.02.16
버블 정렬(Bubble Sort)  (0) 2021.02.16