Data Structure & Algorithm/알고리즘(Algorithm)

04. 이진탐색

jihyeon99 2024. 6. 18. 23:51

1. 이진탐색

기본 아이디어

찾아야 하는 수의 범위 중 가운데의 값과 찾고자 하는 값을 비교하여 대소관계에 따라 특정 구간으로 이동하는 것을 반복합니다. 이때, 찾는 범위 속 원소의 갯수가 1개로 줄어들면 탐색을 종료합니다.

이진탐색을 위해서 배열에 들어있는 값은 반드시 정렬되어 있어야 합니다.

 

상세 과정
  • left ≤ right를 만족할때까지(원소 수가 1개로 줄어들 때까지), 찾는 숫자인 target과 가운데 위치(mid)에 해당하는 값인 arr[mid]를 비교합니다.
    • target == arr[mid]인 경우 : 해당 위치인 mid를 반환합니다.
    • target < arr[mid]인 경우 : right를 mid-1로 움직입니다.
    • target > arr[mid]인 경우 : left를 mid+1로 움직입니다.
  • 반복이 끝났는데도 아직 target의 위치가 반환되지 않았다면, 찾는 숫자인 target이 없다는 표시인 -1을 반환합니다.

 

시간복잡도

이진탐색은 반드시 정렬이 되어야함을 전제로 합니다. 루프를 한 번 돌때마다 구간의 길이는 반으로 감소하고, 구간의 길이가 1이 될때까지 계속 반복합니다. 즉, 루프는 약 logN번 돌게 되므로, 시간복잡도는 O(logN)입니다.

  • 정렬이 되어 있는 경우
    • 탐색의 시간복잡도는 O(logN)입니다.
    • 순차탐색의 시간복잡도 O(N)과 비교할 때 엄청 빠르므로, 기본적으로 정렬이 된 경우엔 값을 찾을 때 이진탐색을 많이 사용합니다.
  • 정렬이 되어 있지 않은 경우
    • 정렬을 하는 데 O(NlogN)의 시간이 소요됩니다. (ex. 퀵 정렬)
    • 탐색을 하는 데 O(logN)의 시간이 소요됩니다.
    • 따라서 정렬과 탐색의 총 시간복잡도는 O(NlogN) + O(logN)입니다.
    • 탐색을 여러 번 진행하는 경우가 아니라면, 이진탐색보다 순차탐색이 유리할 수 있습니다.

 

코드
function binary_search(arr[], target)
  set left = 0
  set right = arr.size - 1
  while left <= right
    set mid = (left + right) / 2
    if arr[mid] == target
      return mid

    if arr[mid] > target
      right = mid - 1
    else
      left = mid + 1
      
  return -1

2. Lower bound, Upper bound

찾는 값 target이 배열에 여러 개 있다면, 이진탐색을 돌렸을 때 어떤 위치가 나오게 될지는 아무도 모릅니다. 이때 lower bound와 upper bound를 활용하면, 특정 값이 존재하는 구간을 알 수 있습니다. [lower bound, upper bound)

Lower bound란?

원하는 값 target 이상이 처음으로 나오는 위치를 의미합니다. 이는 바꿔말해 target보다 같거나 큰 원소의 위치들 중 가장 작은 값을 의미합니다.

function lower_bound(arr, target)
  set left = 0                         // 첫 번째 원소의 위치로 설정합니다.
  set right = arr.size - 1             // 마지막 원소의 위치로 설정합니다.
  set min_idx = arr.size               // 최소이므로, 답이 될 수 있는 값보다 더 큰 값으로 설정합니다.
  while left <= right                  // [left, right]가 유효한 구간이면 계속 수행합니다.
    set mid = (left + right) / 2       // 가운데 위치를 선택합니다.
    if arr[mid] >= target              // 만약에 선택한 원소가 target보다 같거나 크다면 
      right = mid - 1                  // 왼쪽에 조건을 만족하는 숫자가 더 있을 가능성 때문에, right를 바꿔줍니다.
      min_idx = min(min_idx, mid)      // 같거나 큰 값들의 위치 중 최솟값을 계속 갱신해줍니다.
    else
      left = mid + 1                   // 작은 경우라면 left를 바꿔줍니다.

  return min_idx                       // 조건을 만족하는 최소 index 값을 반환합니다.

 

Upper bound란?

원하는 값 target 초과인 값이 처음으로 나오는 위치를 의미합니다. 이는 바꿔말해 target보다 같거나 큰 원소의 위치들 중 가장 작은 값을 의미합니다.

function upper_bound(arr, target)
  set left = 0                         // 첫 번째 원소의 위치로 설정합니다.
  set right = arr.size - 1             // 마지막 원소의 위치로 설정합니다.
  set min_idx = arr.size               // 최소이므로, 답이 될 수 있는 값보다 더 큰 값으로 설정합니다.
  while left <= right                  // [left, right]가 유효한 구간이면 계속 수행합니다.
    set mid = (left + right) / 2       // 가운데 위치를 선택합니다.
    if arr[mid] > target               // 만약에 선택한 원소가 target보다 크다면 
      right = mid - 1                  // 왼쪽에 조건을 만족하는 숫자가 더 있을 가능성 때문에, right를 바꿔줍니다.
      min_idx = min(min_idx, mid)      // 큰 값들의 위치 중 최솟값을 계속 갱신해줍니다.
    else
      left = mid + 1                   // 같거나 작은 경우라면 left를 바꿔줍니다.

  return min_idx                       // 조건을 만족하는 최소 index 값을 반환합니다.

 

Lower/Upper bound를 활용한 target의 개수
  • Lower Bound == UpperBound인 경우
    • 배열 내에 target은 존재하지 않습니다.
  • Lower Bound ≠ Upper Bound인 경우
    • 배열 내에 target은 (Upper Bound - Lower Bound)개 존재합니다.

 

Custom bound

Upper bound를 조금 변형하여, 원하는 값 target 이하의 값이 마지막으로 나오는 위치도 알 수 있습니다. 이는 바꿔말해 target보다 같거나 작은 원소의 위치들 중 가장 큰 값을 의미합니다.

function custom_bound(arr, target)
  set left = 0                         // 첫 번째 원소의 위치로 설정합니다.
  set right = arr.size - 1             // 마지막 원소의 위치로 설정합니다.
  set max_idx = -1                     // 최대이므로, 답이 될 수 있는 값보다 더 작은 값으로 설정합니다.
  while left <= right                  // [left, right]가 유효한 구간이면 계속 수행합니다.
    set mid = (left + right) / 2       // 가운데 위치를 선택합니다.
    if arr[mid] <= target              // 만약에 선택한 원소가 target보다 같거나 작다면 
      left = mid + 1                   // 오른쪽에 조건을 만족하는 숫자가 더 있을 가능성 때문에, left를 바꿔줍니다.
      max_idx = max(max_idx, mid)      // 같거나 작은 값들의 위치 중 최댓값을 계속 갱신해줍니다.
    else
      right = mid - 1                  // 값이 더 큰 경우라면 right를 바꿔줍니다.

  return max_idx                       // 조건을 만족하는 최대 index 값을 반환합니다.

'Data Structure & Algorithm > 알고리즘(Algorithm)' 카테고리의 다른 글

05. 스택, 큐, 덱  (0) 2024.06.20
03. 정렬  (2) 2024.06.11
02. 배열, 연결 리스트  (0) 2024.06.10