Algorithm/알고리즘

분할과 정복 기법 (Divide and Conquer)

olli2 2021. 11. 29. 00:04

분할과 정복 기법  (Divide and Conquer)

 : 하향식 설계 기법

 : 문제의 크기를 작게 나누어서 좀 더 쉽게 해결할 수 있는 상태로 변환하여 문제를 해결하는 방법

 : 하나의 문제를 여러 개의 작은 문제로 나누어 각각에 동일한 해결 기법을 계속적으로 적용한 후, 결과들을 연합하여 원래의 문제를 해결

 : 분할된 부분의 문제들의 크기가 커서 해결이 여전히 쉽지 않을 경우, 각 부분의 문제들에 대해 다시 분할과 정복 기법 적용

 : 각 부분의 문제들이 간단히 해결될 수 있는 상태에 이를 때까지 반복 (순환 : recursion)

 

 재귀함수

 : 분할과 정복 기법을 위한 구현 기술

 : 처리 과정이 분할된 작은 문제들에 서로 동일한 로직이 여러 번 발생하는 것을 방지하기 위해 동적 프로그래밍 기법 사용

 

재귀적 알고리즘

 : 알고리즘이나 함수가 수행 도중에 자기 자신을 다시 호출하여 문제를 해결하는 기법

 : 문제를 같은 형태의 작은 문제로 축소해서 원래의 큰 문제를 해결해가는 방식 (분할과 정복)에 사용

 

재귀적 알고리즘 설계 시 고려사항

 : 재귀호출이 한번 이루어질 때마다 처리할 문제는 계속 작아져야 함

 : 재귀호출이 무한히 계속되지 않고 최종적으로는 끝날 수 있도록 종료 조건이 있어야 함

 

재귀 호출 (recursive call)

 : 프로그래밍에서 함수가 자기 자신을 호출하여 순환 수행되는 것

 : 함수에서 실행해야 하는 작업의 특성에 따라 일반적인 호출방식보다 프로그램의 크기를 줄이고 간단하게 작성 가능

 

재귀 호출 예시

// 합계
public class exSum {
	public static void main(String[] args) {
		System.out.println(sum(10));
	}
    
	static int sum(int n) {
		if(n==0) {
			return 0;
		}
		else {
			return n + sum(n-1);
		}
	}
}
// 피보나치 수열
import java.util.Scanner;

public class exFibonacci {

	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		int n = sc.nextInt();
		
		for (int i=1; i<=n; i++) {
			System.out.print(fib(i) + " ");
		}
		
		sc.close();
	}
	
	static int fib(int n) {
		if (n==1 || n==2) return 1;
		else return fib(n-1) + fib(n-2);
	}
}
// 팩토리얼 계산
import java.util.Scanner;

public class exFactorial {

	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		
		System.out.print("정수 입력 : ");
		int n = sc.nextInt();
		
		System.out.printf("%d! = ", n);
		System.out.print("1 = " + factorial(n));
		
		sc.close();
	}
	
	static int factorial(int n) {
		if (n == 0 || n == 1) return 1;
		else {
			System.out.print(n + " * ");
			return  n * factorial(n-1);
		}
	}
}
// 하노이의 탑
import java.util.Scanner;

public class Hanoi {

	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
        System.out.print("원반 개수 입력 : ");
        int n = sc.nextInt();
        
        hanoi(1, 2, 3, n); //1번 기둥의 n개를 3번 기둥으로 옮김
	}
	
	static void hanoi(int from, int m, int to, int n) { // from -> to로 원판 n이동
		System.out.printf("f:%d m:%d t:%d n:%d\n", from, m, to, n);
		
		if(n == 0)
			return;
		
		hanoi(from, to, m, n-1); // 원판 n-1을 from -> m으로 이동
		System.out.printf("원반 [%d]을 %d에서 %d로 이동\n", n, from, to);//원판 n이 from -> to로 이동
		hanoi(m, from, to, n-1); // 원판 n-1을 m -> to로 이동
	}
}

 

분할과 정복 기법 응용 사례

이진 탐색 (Binary Search; 이분 탐색)

 : 전체 데이터 집합을 두 부분으로 나누어 검색하는 방법

 : 값이 어느 부분에 속하는지를 결정하여 그 부분에 대하여 순환적으로 탐색 수행

 : 정렬된 리스트에서 중간에 위치한 키 값과 찾고자 하는 값이 같으면 탐색 성공,

   다르면, 중간 항목 기준으로 두 부분 중 한쪽 리스트를 선택하여 키를 정한 후 다시 이진 탐색 -> 키 값과 같으면 성공

   여전히 탐색 실패 -> 나머지 다른 한쪽 리스트 이진 탐색

 

이진 탐색 특징

 : 분할 및 정복에 의한 탐색 방법 중 하나

 : 리스트가 정렬되어 있는 경우에 적합

 : 정렬되어 있지 않은 경우, 리스트의 항목들을 정렬 시켜야 함

   -> 레코드의 삽입/삭제 빈번하게 발생되는 경우, 리스트 재구성에 의한 항목들의 이동 요구됨

   -> 데이터의 삽입/삭제가 거의 없는 리스트에 적합

 

2원 합병 정렬 (Two-way merge sort)

 : 정렬된 2개의 리스트를 혼합하여 완전히 정렬된 하나의 리스트로 합하는 정렬 방식

 

퀵 정렬 (Quick sort)

퀵 정렬 설명은 아래 링크 참조

https://olli2.tistory.com/13?category=985942 

 

퀵 정렬 (Quick Sort)

퀵 정렬 (Quick Sort)  : 기준 데이터 (pivot)을 설정하고, 그 기준보다 큰 데이터와 작은 데이터의 위치를 바꾸는 정렬 방법  : 일반적으로 첫 번째 데이터를 pivot으로 설정함  : 일반적인 상황에서

olli2.tistory.com