코딩 테스트(Coding Test)/알고리즘 개념
분할 정복(Divide and Conquer) - Merge Sort
배씌
2024. 11. 13. 16:51
분할 정복이란?
- 문제를 작은 2개의 문제로 분리하고 각각을 해결한 다음, 결과를 모아서 원래의 문제를 해결하는 전략
- 분할 정복은 대개 순환 호출을 이용하여 구현한다.
합병 정렬(merge sort) 개념
- 합병 정렬은 크게 아래 단계로 이루어진다.
- 분할(Divide) : 입력 배열을 같은 크기의 2 배열로 분할
- 정복(Conquer) : 부분 배열을 정렬. 부분 배열의 크기에 따라 다시 재귀 호출하여 분할 정복 실행
- 결합(Combine) : 정렬된 부분 배열을 하나의 배열로 정렬
- 과정
- 추가적인 리스트가 필요함 -> 불필요한 메모리 낭비 가능성 O
- 각 부분 배열 정렬할 때도 합병 정렬을 순환적으로 호출하여 적용
- 합병 정렬에서 실제 정렬이 이루어지는 시점은 2개 리스트를 합병(merge) 하는 단계
합병 정렬 Pseudo code
1. if(p < q) { // 배열의 원소 수가 2개 이상이면
2. k = floor((p+q) / 2) // k는 중간 원소의 인덱스
3. MergeSort(A, p, k) // 앞 부분 순환 호출
4. MergeSort(A, k+1, q) // 뒷 부분 순환 호출
5. A[p]~A[k]와 A[k+1]~A[q]를 합병
6. }
합병 정렬 구현 코드 (.cpp)
- mergeSort() // p : start, q : end, k : mid
void mergeSort(int A[], int start, int end) {
if(start < end) {
int mid = (start + end) / 2 ; // 배열 중간 인덱스를 계산
// 1.배열을 재귀적으로 반으로 나누어 정렬
mergeSort(A, start, mid); // 앞 부분
mergeSort(A, mid + 1, end); //뒷 부분
// 2.두 하위 배열을 병합
merge(A, start, mid, end);
}
}
중간 원소의 인덱스를 mid에 저장 후, mid를 기준으로 앞부분과 뒷부분으로 분할 후 합병(merge) 하였음
- merge()
void merge(int A[], int start, int mid, int end) {
int *temp = new int[end - start + 1]; // 임시 배열 생성
int i = start; // 앞 부분 배열 시작 인덱스
int j = mid + 1; // 뒷 부분 배열 시작 인덱스
int k = 0; // 임시 배열 인덱스
// 1. 앞, 뒤 배열 값 중 더 작은 값을 tmp로 복
while (i <= mid && j <= end) {
if (A[i] <= A[j]) {
temp[k] = A[i];
i++;
}
else {
temp[k] = A[j];
j++;
}
k++;
}
// 2. 앞 배열에 남은 값 있으면 temp로 복사
if (i > mid) {
while (j <= end) {
temp[k] = A[j];
k++;
j++;
}
}
// 3. 뒷 배열 남은 값 있으면 temp로 복사
else {
while(i <= mid) {
temp[k] = A[i];
k++;
i++;
}
}
// 4. temp 배열 -> 원본 배열로 복사
for(int i=start; i<=end; i++)
A[i] = temp[i - start];
delete[] temp;
}
앞서 분할한 배열의 크기 만큼 temp 임시 공간 할당.
while 루프에서 두 배열의 첫 원소 값을 비교하여 더 작은 값을 temp 배열에 복사 후, 인덱스 값을 증가 시키면서 A[i], A[j] 값을 비교하며 temp에 복사 반복. 두 배열 중 하나가 먼저 끝나면, 다른 배열에 남은 원소들을 temp 배열로 복사. 이후 원본 배열로 temp 배열 값들을 복사하여 결과 도출.