ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 선택문제 Quick Select
    Algorithm/Theory 2021. 1. 7. 15:44
    - 선택문제: n개의 값 중에서 k번째로 크거나 작은 수를 찾는 문제

    - Quick Select: pivot작은 값, 같은 값, 큰 값으로 나누어서 찾고자 하는 수가 어디에 속해있는지 찾아나가는 방법
    1. pivot 고르기
    2. 3부분으로 나누기 ( n-1번 수행 )
        S = { P보다 작은 값 }
        L = { P보다 큰 값 }
        M = { P와 같은 값 }
    3. k가 어디에 속해 있는지 찾기
        |S| : 배열 S의 개수,
        (S에 속함)  if |S| >= k  { return 재귀(S, k) }
        (L에 속함)  else if |S| + |M| < k  { return 재귀(L, k - |S| - |M| }
        (M에 속함) else { return P }

    - 시간복잡도
    최선의 경우 O(n), 최악의 경우 O(n^2) 최악의 경우는 극히 드물어서 평균적으로 O(n)으로 생각한다.

     

    Quick Select

    n개의 값에서 k번째로 작은 수를 찾는 문제를 선택문제 라고 하는데요,
    선택 문제를 해결하는 방법 중 Quick Selection에 대해 알아보겠습니다.

    아이디어는 
    pivot을 기준으로 아래의 3가지 부분으로 나눠서 k가 어디에 속해 있는지 생각해보는 것입니다.

    small: pivot보다 작은 값,
    big: pivot보다 큰 값,
    same: pivot과 같은 값

    배열을 쭉 돌면서 나누면 n-1로 가능하겠죠?
    다음으로 k가 어디에 속해있는지 보는 것입니다. 각각의 배열의 크기로 범위를 생각해볼 수 있겠네요.

    small: small.count >= k
    big: small.count + same.count < k
    same: 나머지

    재귀적으로 이 과정을 반복해주면 된답니다 :)

    구현

    pivot을 그냥 배열의 첫 번째 요소로 생각하면 다음과 같이 구현할 수 있습니다.

    func quickSelect(_ n: [Int], _ k: Int) -> Int {
        var small = [Int]()
        var same = [Int]()
        var big = [Int]()
        
        let pivot = n[0]
        
        for num in n {
            if num == pivot {
                same.append(num)
            } else if num < pivot {
                small.append(num)
            } else {
                big.append(num)
            }
        }
        
        if small.count >= k {
            return quickSelect(small, k)
        } else if small.count + same.count < k {
            return quickSelect(big, k - small.count - same.count)
        } else {
            return pivot
        }
    }

     

    시간 복잡도

    Best case O(n)

    피벗을 기준으로 양쪽이 균등하게 나눠진다면
    나누어진 절반 중에서만 찾으면 되니까 T(n/2), 분배하는 횟수를 n으로 생각하면

    T(n) = T(n/2) + n

    로 생각할 수 있고, 절반의 절반을 또 찾으면 되니까 T(n/2) = T(n/2^2) + n/2로 생각할 수 있고 아래와 같은 식을 도출할 수 있습니다.

    T(n) = T(n/2) + n
           = T(n/2^2 + n/2) + n
           = T(n/2^k) + n/2^(k-1) + ... + n/2 + n

    이때 n = 2^k로 가정하면, T(n/2^k) = T(1) = 1 로 생각하고 지워버릴 수 있습니다. 그러면

    T(n) = T(n/2) + n
           = T(n/2^2 + n/2) + n
           = T(n/2^k) + n/2^(k-1) + ... + n/2 + n 
           = n(1 + 1/2 + 1/2^2 + ... + 1/2^k) 

    가 되고 이때 (1 + 1/2 + 1/2^2 + ... + 1/2^k) 부분은 1을 넘을 수가 없습니다.

    따라서 O(n) 의 시간복잡도로 생각할 수 있습니다.

    Worst case O(n^2)

    그러면 최악의 경우를 생각해볼까요?
    피벗을 기준으로 한쪽으로 모두 치우쳐져 있다면 최악의 경우엔
    T(n) = T(n-1) + n 이 되므로 최악의 경우 n^2 을 돌아야합니다.

    Average O(n)

    하지만 이 경우는 극히 드물기 때문에 Quick Select은 평균적으로 O(n)으로 생각할 수 있다고 합니다.

     

    참고

    신찬수 교수님 Youtube

    감사합니다 :)

    'Algorithm > Theory' 카테고리의 다른 글

    SegmentTree  (0) 2021.04.08
    RingBuffer  (0) 2021.04.07
    Combination 조합  (3) 2020.08.25
    Floyd Wharshall 플로이드 와샬  (2) 2020.06.18
    Binary Search 이진탐색(2) Lower/Upper bound  (0) 2020.06.12

    댓글

Designed by Tistory.