
참고: https://olrlobt.tistory.com/45
문제의 특성에 따라 분할정복 알고리즘과 동적 계획법 중 적합한 알고리즘을 선택하여 사용하는 것이 중요하다.
일반적으로 분할정복 알고리즘은 다음 세 단계로 구성된다.


import java.util.Arrays;
public class Main {
private static int[] tmp;
public static void mergeSort(int[] a) {
if (a == null || a.length < 2) return;
tmp = new int[a.length];
mergeSort(a, 0, a.length - 1);
}
private static void mergeSort(int[] a, int l, int r) {
if (l >= r) return;
int m = (l + r) >>> 1; // (l+r)/2 overflow 방지
mergeSort(a, l, m);
mergeSort(a, m + 1, r);
merge(a, l, m, r);
}
private static void merge(int[] a, int l, int m, int r) {
int i = l, j = m + 1, k = l;
while (i <= m && j <= r) {
if (a[i] <= a[j]) tmp[k++] = a[i++];
else tmp[k++] = a[j++];
}
while (i <= m) tmp[k++] = a[i++];
while (j <= r) tmp[k++] = a[j++];
System.arraycopy(tmp, l, a, l, r - l + 1);
}
public static void main(String[] args) {
int[] src = {1, 9, 8, 5, 4, 2, 3, 7, 6};
mergeSort(src);
System.out.println(Arrays.toString(src));
}
}
출처:
https://yunmap.tistory.com/entry/알고리즘-Java로-구현하는-쉬운-Merge-Sort-병합-정렬-합병-정렬
[현미와 백미는 섞어먹자.:티스토리]

import java.util.Arrays;
public class Main {
public static void main(String[] args) {
int[] arr = {3, 5, 2, 7, 1, 4, 6};
int[] tmp = new int[arr.length];
mergeSort(arr, tmp, 0, arr.length - 1);
System.out.println("합병 정렬: " + Arrays.toString(arr));
}
public static void mergeSort(int[] arr, int[] tmp, int left, int right) {
if (left >= right) return;
int mid = (left + right) >>> 1;
mergeSort(arr, tmp, left, mid);
mergeSort(arr, tmp, mid + 1, right);
merge(arr, tmp, left, mid, right);
}
private static void merge(int[] arr, int[] tmp, int left, int mid, int right) {
int i = left, j = mid + 1, k = left;
// 두 포인터로 병합 (안정성: 왼쪽 <= 오른쪽이면 왼쪽 먼저)
while (i <= mid && j <= right) {
if (arr[i] <= arr[j]) tmp[k++] = arr[i++]; // stable
else tmp[k++] = arr[j++];
}
while (i <= mid) tmp[k++] = arr[i++];
while (j <= right) tmp[k++] = arr[j++];
System.arraycopy(tmp, left, arr, left, right - left + 1);
}
}
n-1 + n-2 + … + 1 = n(n-1)/2 → O(n²)[31, 1, 26] → 중앙값 26을 피봇.핵심 메시지: 좋은 피봇(중앙값 근처)을 선택하고, 작은 구간에서 삽입 정렬로 스위칭하면, 실제 실행 시간(상수항)이 유의하게 줄어든다.

[파라메트릭 서치 (이분탐색)] 란 ?
최적화 문제를 결정 문제로 바꾸어 해결하는 기법이다. 결정 문제란, '예' 혹은 '아니오'로 답하는 문제를 말한다. '주어진 범위에서 원하는 조건을 만족하는 가장 알맞은 값을 찾는 문제'에 주로 파라메트릭 서치를 사용한다.
int binarySearch (int arr[], int low, int high, int key) {
while (low <= high) {
int mid = low + (high-low) / 2;
if (arr[mid] == key) // 종료 조건1 검색 성공.
return mid;
else if (arr[mid] > key)
high = mid - 1;
else
low = mid + 1;
}
return -1 ; // 종료 조건2 (low > high) 검색 실패.
}
int binarySearch (int arr[], int low, int high, int key) {
if (low > high) // 종료 조건2 검색 실패.
return -1;
int mid = low + (high-low)/2;
if (arr[mid] == key) // 종료 조건1 검색 성공.
return mid;
else if (arr[mid] > key)
return binarySearch(arr, low, mid-1, key);
else
return binarySearch(arr, mid+1, high, key);
}

package io.github.ones1kk.study.sorting;
public class InsertionSort {
public static void insertionSort(int[] arr, int size) {
for (int i = 1; i < size; i++) {
int target = arr[i];
int j = i - 1;
while (j >= 0 && target < arr[j]) {
arr[j + 1] = arr[j];
j--;
}
arr[j + 1] = target;
}
}
}
분할정복은 Top-down방식으로 재귀 호출의 장단점과 똑같다고 보면 된다.
분할정복 알고리즘 장점:
분할정복 알고리즘 단점:
분할정복 알고리즘은 많은 문제에 대해 매우 효과적이며, 많은 응용 분야에서 사용된다. 하지만 구현이 복잡하고 추가적인 메모리 요구가 있을 수 있으므로, 적용 전에 장단점을 고려해야 한다.
| 장점 | 단점 |
|---|---|
| Top-down 재귀방식으로 구현하기 때문에 코드가 직관적. | 재귀 함수 호츨로 오버헤드가 발생할 수 있다. |
| 문제를 나누어 해결한다는 특징상 병렬적으로 문제를 해결할 수 있다. | ∙ 스택에 다량의 데이터가 보관되는 경우 오버플로우가 발생할 수 있다. |
참고:
https://loosie.tistory.com/237
https://gmlwjd9405.github.io/2018/05/10/algorithm-quick-sort.html
난이도 별로 한 문제씩 준비해봤다. 브론즈로 먼저 분할 정복 알고리즘이 어떤 느낌인 지 익히고, 실버 문제로 직접 알고리즘을 문제에 적용 시키는 연습을 한 후 골드 문제로 심화과정을 익혀보도록 하겠다.
https://www.acmicpc.net/problem/13277

백준에서 분할 정복으로 분류된 유일한 브론즈 문제이다 !
브론즈 문제가 이 문제 뿐이라 가져오긴 했지만 굉장히 당황스럽다.
매우 간단한 문제이지만 …. 나는 틀리긴 했다 …
사실 이게 분할 정복 알고리즘이랑 무슨 연관인 지는 모르겠다 !!!
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.math.BigInteger;
import java.util.StringTokenizer;
public class Main {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st = new StringTokenizer(br.readLine());
BigInteger n = new BigInteger(st.nextToken());
BigInteger m = new BigInteger(st.nextToken());
System.out.println(n.multiply(m));
}
}
https://www.acmicpc.net/problem/2630

이번에 풀어볼 문제는 2630의 색종이 문제이다 !!
이 문제를 풀 때는 흰색과 파란색 정사각형 색종이의 개수를 알아내야 하는데, 규칙은 이렇다.
부분 색종이는 모두 같은 색상이어야 한다.
만약 모두 같은 색상이 아닐 경우 색종이를 잘라야 한다.
색종이를 자를 때는 1/2 씩, 즉 절반씩 잘라서 정사각형을 얻어야 한다.

위의 조건대로 문제를 풀자면 이러한 그림의 형태를 얻을 수 있다!!
코드를 구성할 때 중요한 점은
각각의 행과 열의 시작점(초기는 (0, 0)이 기준)에서 현재 파티션에 대하여 모두 같은 색상인지 체크를 먼저 해야한다.
색상이 같다면 해당 색상의 개수를 1 증가시키고 함수를 종료한다.
색상이 같지 않다면, 4등분 하여 각 부분 문제로 쪼개어 문제를 푼다.
이러한 조건들로 코드를 구성하자면 다음과 같은 코드를 얻을 수 있다.
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;
public class Main {
static int white = 0;
static int blue = 0;
static int[][] paper;
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
int N = Integer.parseInt(br.readLine());
paper = new int[N][N];
StringTokenizer st;
for(int i = 0; i < N; i++) {
st = new StringTokenizer(br.readLine());
for(int j = 0; j < N; j++) {
paper[i][j] = Integer.parseInt(st.nextToken());
}
}
divied(0, 0, N);
System.out.println(white);
System.out.println(blue);
}
public static void divied(int x, int y, int n) {
boolean flag = true;
// 색종이 통일 되어 있는지 탐색
for(int i = 0; i < n; i++) {
for(int j = 0; j < n; j++) {
// 다른 색 하나 있을 경우
if(paper[x][y] != paper[x + i][y + j]) {
flag = false;
break;
}
if(!flag) break;
}
}
// 탐색한 영역이 한가지 색으로 통일된 경우
if (flag) {
if(paper[x][y] == 0) {
white++;
}else {
blue++;
}
}else {
divied(x, y, n / 2);
divied(x + n / 2, y, n / 2);
divied(x, y + n / 2, n / 2);
divied(x + n / 2, y + n / 2, n / 2);
}
}
}
https://www.acmicpc.net/problem/1074

다음은 백준 골드5의 문제이다… 내 실력으론 혼자서 골드 문제를 풀 수 없다. 골드 5인만큼 난이도가 상당해서 같이 차근차근 풀어보는 것이 좋을 것 같아서 가져왔다!!
문제는 간단하게, Z 모양으로 반복되는 패턴에서,
0행 0열부터 시작한다면, 3행 3열은 몇 번째 숫자를 나타 내는지를 구하는 문제이다.
이 문제를 분할정복을 이용하여 해결하여 봅시다...

일단은 숫자는 신경 쓰지 말고 사각형의 크기에만 집중해 보자. 위의 그림은 반복되는 패턴이 있으므로 4 등분하여 문제를 작게 분할해보자.

이 중, 구하고자 하는 3행 3열은 첫 번째 사각형에 위치한다. 하지만 3행 3열에 집중하지 말고, 사각형에만 집중해 보자.
그리고 보면 이 사각형을 한 번 더 나눌 수 있을 것 같다 !! 아까와 같이 똑같이 반복되는 패턴 4개로 작게 분할 한다.

이 그림에서도 숫자가 아니라 사각형에 집중하자. 엥 !!!! 아니 이것도 뭔가 나눌 수 있을 것 같다 ?? 한 번만 더 나눠보자 !!!.

이제 더 이상 나눌 수 없을 것 같다 !! 이 것이 가장 작게 나눈 가장 작은 문제가 된다. 현재 그림에는 숫자가 쓰여 있어서 헷갈릴 수 있지만,
가장 작은 사각형 중에 네 번째 사각형이므로 실제 우리가 구한 값은 아래의 그림.

즉 해결 단계에서, 3의 값을 구한 것이다.
가장 작은 문제를 해결했으므로, 이제는 작은 문제들을 결합시킨다.

내가 구한 것은 가장 작은 문제에서 3의 값을 나타내지만, 이 작은 문제들의 결합인 위의 문제에서는 네 번째 그림을 나타낸다.
각 사각형의 첫 번째 값이 4 단위로 증가하기 때문에,
(가장 작은 사각형에서 구한 값 3) + (네 번째 사각형 4-1) x ( 단위 4) = 15의 값을 구할 수 있다.

마지막 위의 결합 부분에서는 구하고자 하는 결과가 첫 번째 사각형 이기 때문에, 그대로 15라는 답이 도출된다.
만약, 위 문제가 3행 3열이 아닌, 7행 3열을 구하는 문제였다면, 위 그림에서 세 번째 사각형에 위치한다.
각 사각형이 16 단위로 증가하기 때문에,
(이 전 사각형에서의 값 15) + ( 세 번째 사각형 3-1 ) x ( 단위 16) = 47의 값이 도출된다.
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;
public class Main {
static int count = 0;
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st = new StringTokenizer(br.readLine());
int N = Integer.parseInt(st.nextToken());
int r = Integer.parseInt(st.nextToken()); //행
int c = Integer.parseInt(st.nextToken()); //열
int size = (int) Math.pow(2, N); //한 변의 사이즈
find(size, r, c);
System.out.println(count);
}
private static void find(int size, int r, int c) {
if(size == 1)
return;
if(r < size/2 && c < size/2) {
find(size/2, r, c);
}
else if(r < size/2 && c >= size/2) {
count += size * size / 4;
find(size/2, r, c - size/2);
}
else if(r >= size/2 && c < size/2) {
count += (size * size / 4) * 2;
find(size/2, r - size/2, c);
}
else {
count += (size * size / 4) * 3;
find(size/2, r - size/2, c - size/2);
}
}
}