본문 바로가기
카테고리 없음

2장 Divide-and-Conquer

by 도리에몽 2022. 9. 29.

Divide & Conquer(분할정복식) 설계 전략

  • 분할(Divide): 해결하기 쉽도록 문제를 여러 개의 작은 부분으로 나눔
  • 정복(Conquer): 나눈 작은 문제를 각각 해결
  • 통합(Combine): (필요하다면) 해결된 해답을 모음

재귀적(recursive) 방법으로 분할하고 정복하여 해결함

🤔 이러한 문제 해결 방법을 무엇이라고 할까요?

 

Top-Down(하향식) 접근 방식

구조 위주(모듈 단위)

절차지향(구조적) 프로그래밍 (ex. C언어)

🤔  top-down 방식의 장점을 생각해봅시다.

 

Bottom-Up(상향식) 접근 방식

데이터 위주(객체 단위)

객체지향(데이터) 프로그래밍 (ex. C++, JAVA 언어)

🤔  bottom-up 방식의 장점을 생각해봅시다.

Divide & Conquer

Divide & Conquer을 사용한 알고리즘은 항상 재귀적인 알고리즘을 갖고 있습니다.

💡 Divide&Conquer 알고리즘을 의사 코드로 짜 봅시다.

procedure D&C(p, q)

int n, A(1:n);
int m, p, q;  /* 1≤p≤q≤n */
If SMALL(p, q) then return(G(q, p))
else 
{ m := DIVIDE(q, p)
  return COMBINE (D&C(p, m), D&C(m+1, p))
}

End if

 

Divide & Conquer 시간복잡도

T(n) = { g(n), if n is small enough

             f(n) + 2T(n/2), otherwise

🤔  각각의 T(n), f(n), g(n)이 의미하는 바를 생각해봅시다.

 

Binary Search (이분검색)

  • Divide: breaking up three instances,
    • I1= (S[1], S[2], …, S[k-1]), x), I2 = (S[k], x), I3 = (S[k+1], …, S[n], x)
  • Conquer:
    • If x = S[k], then location = k
    • If x < S[k], then only I1 remains to be solved (for I2 & I3, location = 0)
    • If x > S[k], then only I3 remains to be solved (for I1 & I2, location = 0)
  • Merge: (필요 없음)

 

Recursive 알고리즘으로 나타낸 Binary Search

💡 Divide&Conquer 알고리즘을 의사 코드로 짜 봅시다.

index BYSRCH(index low, index high) {
	index mid;
	if(low > high) return 0;
	else
	{ mid := (low + high) / 2     
		if(x == S[mid]) return mid;  
		else if(x < S[mid])
			return BYSRCH(low, mid-1);  
		else
			return BYSRCH(mid+1, high);
    }
}

 

💡 배열 [ -30, -2, 0, 4, 7, 34, 64, 87, 99] 이고 X=64일 때 Binary Search로 풀어봅시다.

 

Analysis of Binary Search (recursive algorithm)

T(n) = { 1              if n=1

              T(n/2)+1 if n>1

Let n = 2^k

T(n) = T(n/n) + k = 1 + k = 1 + logn ∈ O(logn) O(logn)

 

정렬 알고리즘

  • 평균적으로 O(n^2) 시간이 소요되는 알고리즘
    • 선택정렬
    • 버블정렬
    • 삽입정렬
  • 평균적으로 O(n) 시간이 소요되는 알고리즘
    • 합병정렬 (merge sort)
    • 퀵정렬 (Quick sort)
    • 힙정렬 (Heap sort)

 

합병정렬(Mergesort)

  • 합병 정렬 (Merge Sort)은 입력이 2개의 부분 문제로 분할하며(divide), 부분문제의 크기가 1/2로 감소하는 분할 정복 알고리즘
  • n개의 숫자들은 n/2개씩 2개의 부분문제로 분할하고, 각각의 부분문제를 재귀적으로 합병 정렬한 후, 2개의 정렬된 부분을 합병(merge)하여 정렬함
  • 합병 과정이 문제를 정복(conquer)하는 것
void mergesort (in n, keytype S[]) {
	const int h = n/2, m = n-h;
    keytype U[1..h], V[1..m];
    
    if (n > 1) {
    	copy S[1] through S[h] to U[1] through U[h];
        copy S[h+1] through S[n] to V[1] through V[m];
        mergesort(h, U);
        mergesort(m, V);
        merge(h,m,U,V,S);
    }
}

 

🤔  입력 크기가  n = 8인 배열 A=[20, 90, 22, 12, 78, 33, 24, 19] 을 Mergesort로 정렬해봅시다.

 

Mergesort의 시간 복잡도 (최악의 경우)

T(n) = { c                     for n = 1 (c는 상수)

             2T(n/2) + an   for n > 1 (a는 상수)

Let n = 2^k

T(n) = nT(1) + ank =  cn + ank  = cn + anlogn  ➡ O(nlogn)

 

Mergesort의 공간 복잡도 (최악의 경우)

제자리정렬(in-place sort) 알고리즘: 입력 저장 시 필요한만큼 저장소 사용, 추가로 더 사용하지 않음.

Mergesort 알고리즘은 입력인 배열 S이외 U와 V를 추가로 만들어 사용함. 

Mergesort를 재귀호출할 때마다 크기가 S의 반이 되는 U와 V가 추가적으로 필요함.

추가적으로 필요한 저장소 크기: n + n/2 + n/4 + ... = 2n

2n ∈ O(n)