분할과 정복, 정렬

이상민·2021년 4월 17일
0

1. 분할과 정복

분할과 정복의 문제해결 과정
1) 문제를 작은 문제로 나눈다
2) 작은 문제들의 해를 재귀적으로 구한다
3) 작은 문제들의 해를 이용해 문제의 해를 구한다

1-1. 배열에서 최대값 찾기

def getMax(A, first, last):
    if first == last:
        return A[first]
    else:
        mid = (first + last) // 2
        lmax = getMax(A, first, mid)
        rmax = getMax(A, mid+1, last)
        return max(lmax, rmax)

시간복잡도 :

T(n) = 1           if n = 1
     = 2T(n/2) + 4 if n > 1
     
T(n) = 2T(n/2) + 4
     = 2[2T(n/2^2) + 4] + 4
     = 2^2[2T(n/2^3) + 4] + 8 + 4
     = 2^k(T(n/2^k) + (2^k - 1) * 4
     n/2^k = 1 일때
     = n(T(1)) + (n - 1) * 4
     = n + 4n -4
     : O(n)

1-2. 이진탐색 - 원소의 위치

def getIndex(A, left, right, item):
    if left > right:    # 원소 없음
        return -1
    
    else:
        mid = (left+right) // 2
        if A[mid] == item:
            return mid
        elif item > A[mid]:
            return getIndex(A, mid+1, right, item)
        else:
            return getIndex(A, left, mid-1, item)

시간복잡도 :

T(n) = 1            if n = 1
     <= T(n/2) + 1  if n > 1
     <= T(n/2^k) + k
     n/2^k = 1 일때
     = T(1) + log n
     : O(log n)

2. 점화식의 시간복잡도 계산

ex1)

f(n) = a            if n = 1
     = f(n-1) + bn  if n > 1
     
f(n) = f(n-1) + bn
     = [f(n-2) + b(n-1)] + bn = f(n-2) + b(n-1) + bn
     = [f(n-3) + b(n-2)] + b(n-1) + bn = f(n-3) + b(n-2) + b(n-1) + bn
     = f(n-k) + b[(n-(k-1)) + ... + (n-2) + (n-1) + n]
     = f(n-k) + b(n-k+1 + n)k/2
     n-k = 1일때
     = f(1) + b(1+1 + n)(n-1)/2
     = a + b(n+2)(n-1)/2
     : O(n^2)

ex2)

f(n) = a            if n = 1
     = 2f(n/2) + b  if n > 1
     
f(n) = 2f(n/2) + b
     = 2[2f(n/2^2) + b] + b = (2^2)f(n/2^2) + 2b + b
     = (2^k)f(n/2^k) + b(2^k - 1)
     n/2^k = 1일때
     = nf(1) + b(n-1)
     : O(n)

ex3)

f(n) = a               if n = 1
     <= 2f(n/2) + bn   if n > 1
     
f(n) <= 2f(n/2) + bn
     <= 2[2f(n/2^2) + b(n/2)] + bn = (2^2)f(n/2^2) + 2bn
     <= (2^k)f(n/2^k) + kbn
     n/2^k = 1일때
     <= nf(1) + logn * b * n
     : O(n log n)

⚠️ 수열의 합


3. 분할과 정복을 이용한 정렬

3-1. 병합 정렬

단계
1) n 크기 배열 A를 크기가 n/2인 두 배열로 나눈다
2) 크기가 n/2인 배열들을 재귀적으로 정렬한다
3) 정렬된 두 부분을 하나로 병합한다

def mergeSort(A):
    def sort(left, right):
        if (right - left < 2):
            return
        mid = (left+right) // 2
        sort(left, mid)
        sort(mid, right)
        merge(left, mid, right)
  
    def merge(left, mid, right):
        i = left
        j = mid
        tmp = []
    
        # 왼쪽 리스트와 오른쪽 리스트 원소 비교 후 복사
        while (i < mid and j < right):
            if A[i] <= A[j]:
                tmp.append(A[i])
                i += 1
            else:
                tmp.append(A[j])
                j += 1
    
        # 남은 원소 복사
        while (i < mid):
            tmp.append(A[i])
            i += 1
        
        while (j < right):
            tmp.append(A[j])
            j += 1
            
        for i in range(left, right):
            A[i] = tmp[i-left]
            
    return sort(0, len(A))

시간복잡도 :

T(n) = 0             if n = 1
     <= 2T(n/2) + n  if n > 1
     
T(n) <= 2T(n/2) + n
     <= 2[2T(n/2^2) + n/2] + n
     <= (2^k)T(n/2^k) + kn
     n/2^k = 1 일때
     <= nT(1) + log n * n 
     <= n + n log n
     : O(n log n)
  • 병합정렬의 단점
    i) merge 시 O(n) 추가 메모리가 필요하다
    ii) 재귀 때문에 O(log n) 스택 메모리가 필요하다

3-2. 퀵 정렬

단계
1) 배열 A에서 pivot을 선택한다
2) 배열 A를 pivot보다 같거나 작은 원소의 배열, pivot보다 큰 원소의 배열로 나눈다
3) 분할된 두 리스트를 재귀적으로 정렬한다

def quickSort(A, left, right):
    if left < right:
        pivotPoint = partition(A, left, right)
        quickSort(A, left, pivotPoint-1)
        quickSort(A, pivotPoint+1, right)
  • pivot 기준으로 배열을 나누는 partition()의 구현이 중요하다

1) 첫번째 원소를 pivot으로

def partition(A, left, right):
    pivot = A[left]
    i = left+1
    j = right
    while i <= j:
    
        # i는 오른쪽으로 이동하며 pivot보다 큰 수 탐색
        while i <= right and A[i] < pivot:
            i += 1
            
        # j는 왼쪽으로 이동하며 pivot보다 작은 수 탐색
        while j >= left+1 and A[j] >= pivot:
            j -= 1
            
        # i와 j 탐색 영역이 넘어가지 않았다면 교환 후 루프
        if i <= j:
            tmp = A[i]
            A[i] = A[j]
            A[j] = tmp
            i += 1
            j -= 1
            
    # pivot을 위치에 맞게 바꾸고 pivotPoint인 j 반환
    A[left] = A[j]
    A[j] = pivot
    return j

2) 마지막 원소를 pivot으로

def partition(A, left, right):
    pivot = A[right]
    pivotPoint = left-1
    for i in range(left, right):
        if A[i] <= pivot:
            pivotPoint += 1
            exchange(pivotPoint, i)
            
    tmp = A[right]
    exchange(pivotPoint+1, right)

시간복잡도 :

W(n) = W(n-1) + bn
     = [W(n-2) + b(n-1)] + bn
     = W(n-k) + b(n-k+1 + n)k/2
     : O(n^2)
profile
편하게 읽기 좋은 단위의 포스트를 추구하는 개발자입니다

0개의 댓글