ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 이분 탐색(Binary Search) 구현
    Problem Solving 2023. 6. 17. 17:50

     

    https://www.acmicpc.net/blog/view/109

     

    이분 탐색(Binary Search) 헷갈리지 않게 구현하기

    개요 이분 탐색은 off-by-one error가 발생하기 쉬워서 늘 헷갈립니다. 이분 탐색 문제를 풀다보면 탈출 조건으로 lo <= hi, lo < hi, lo + 1 < hi 중 어느 걸 선택해야 할 지, 정답이 lo인지 hi인지 (lo + hi) / 2

    www.acmicpc.net

     

    이분 탐색 구현할 때마다 항상 헷갈려서 시간이 걸리게 되는데, 위의 글 읽고 정리해본다.

     

    구현 방법 

    먼저 결정 문제 설정과 분포 파악해야한다.

     

    문제마다 결정 문제가 다른데 이를 먼저 설정하고, 결정 문제에 따른 True, False 결과의 분포를 생각해본다.

     

    예를 들면 1~50번의 카드 중 28번 카드를 찾는다고 하자.

    1~50번의 카드를 v[i] 찾는 카드를 val 이라 할 때, 결정 문제는 val <= v[i]가 되고 분포는 F, F, ... F, T(i == 28), T, ... T 가 된다.

     

    lo+1 < hi 동안 범위를 줄여나가는데 그 동안 check(lo) (=F)와 check(hi) (=T)의 값은 바뀌지 않게 한다.

    check(lo) == check(mid) 라면 lo = mid를, check(hi) == check(mid)이면 hi = mid를 해주면 된다.

    또한 lo+1 < hi 이기 때문에 lo와 hi 사이에는 무조건 한 개 이상의 칸이 존재, lo < mid < hi를 만족한다.

    lo+1 == hi가 되면 반복문을 탈출한다.

     

    반복문을 탈출했다면 lo+1 >= hi 인데, 항상 lo < mid < hi 이기 때문에 lo < hi 이면서 lo+1 >= hi를 만족하는 경우는 lo+1 == hi 밖에 없음.

     

    이분 탐색이 끝나고 lo, hi 는 F, T의 경계에 위치하게 되고 

    가장 큰 F를 찾는 문제라면 lo, 가장 작은 T를 찾는 문제라면 hi를 반환한다.

     

    1. 결정 문제를 check() 함수라 할 때, check(lo) != check(hi)가 되도록 lo, hi 설정

     

    2. check(lo) == check(mid) 면 lo = mid , 아니면 hi = mid

     

    3. lo + 1== hi 가 되면서 탈출. lo, hi 는 경계에 위치

     

    4. 문제에 따라 lo , hi 반환 

     

     

    bool check(int mid, int target) {
    	if(v[mid] >= target) return true;
    	return false;
    }
    
    
    int binary_search(int lo, int hi) {
    
    	// check(lo) == false != check(hi) == true
        // F, ... , F, T, ... , T
    
    	while (lo + 1 < hi) {
        	int mid = (lo + hi) / 2; // lo < mid < hi 를 항상 만족
            if(!check(mid)) lo = mid;
            else hi = mid;
        }
        
        // lo+1 == hi 가 되며 탈출하고 lo, hi는 경계에 위치
        // 문제에 따라 lo, hi 반환
        return hi;
    }

     

     

    lower_bound, upper_bound

    lower_bound는 v[i-1] < k <= v[i]인 i를 찾아주는 함수로, k <= v[i]인 i의 최솟값을 반환.

    만약 v의 모든 원소가 k보다 작다면 v의 마지막 다음칸의 위치를 반환.

     

    upper_bound는 v[i-1] <= k < v[i]인 i를 찾아주는 함수로, k < v[i] 인 최솟값을 반환.

    만약 모든 원소가 k보다 작거나 같다면 v의 마지막 다음칸의 위치를 반환.

    #include <bits/stdc++.h>
    #define fastio cin.tie(0)->sync_with_stdio(0)
    using namespace std;
    
    int LowerBound(const vector<int>& v, int x) {
        const int n = v.size();
        int lo = -1, hi = n;
        while (lo + 1 < hi) {
            int mid = (lo + hi) / 2;
            if (!(v[mid] >= x)) lo = mid;
            else hi = mid;
        }
        return hi;
    }
    
    int UpperBound(const vector<int>& v, int x) {
        const int n = v.size();
        int lo = -1, hi = n;
        while (lo + 1 < hi) {
            int mid = (lo + hi) / 2;
            if (!(v[mid] > x)) lo = mid;
            else hi = mid;
        }
        return hi;
    }
    
    int main() {
        fastio;
        vector<int> v = { 1, 2, 3, 3, 4 };
        cout << LowerBound(v, 3) << '\n'; // 2
        cout << UpperBound(v, 3) << '\n'; // 4
        cout << UpperBound(v, 3) - LowerBound(v, 3) << '\n'; // 2
    }

    (hi는 v의 모든 원소가 k보다 작은(작거나 같은) 경우 n을 반환해야 하기 때문에 처음에 n이상으로 설정해야 합니다. 또한 hi는 최소 0까지 감소할 수 있어야 하기 때문에 lo = -1로 설정해야 합니다)

Designed by Tistory.