|
| 1 | +--- |
| 2 | +title: 이분 탐색 (Binary Search) 알고리즘 |
| 3 | +description: 이분 탐색 (Binary Search) 알고리즘에 대해 정리한 페이지입니다. |
| 4 | +date: 2024-02-09 00:00:00 +/-TTTT |
| 5 | +categories: [Algorithms] |
| 6 | +tags: [algorithm] |
| 7 | +math: true |
| 8 | +toc: true |
| 9 | +pin: false |
| 10 | +image: |
| 11 | + path: /assets/img/algorithms/computer.avif |
| 12 | +comments: true |
| 13 | +--- |
| 14 | + |
| 15 | +<blockquote class="prompt-info"><p><strong><u>Tags</u></strong><br/> |
| 16 | +Algorithm</p></blockquote> |
| 17 | + |
| 18 | +## 개요 |
| 19 | + |
| 20 | +`이분 탐색(Binary Search)` 알고리즘에 대해 정리한 페이지입니다. |
| 21 | + |
| 22 | +## 이분 탐색 (Binary Search) 알고리즘 |
| 23 | + |
| 24 | +### 개념 |
| 25 | + |
| 26 | +`이분 탐색` 또는 `이진 탐색` 알고리즘은 정렬된 배열이나 리스트에서 원하는 값을 빠르게 찾는 알고리즘입니다. <b>정렬된 배열이나 리스트</b>를 같은 크기의 두 부분으로 나누고 필요한 부분에서만 탐색하도록 탐색 범위를 <b>절반</b>씩 줄여가며 원하는 값을 빠르게 찾습니다. |
| 27 | + |
| 28 | +### 특징 |
| 29 | + |
| 30 | +이분 탐색 알고리즘의 특징은 다음과 같습니다. |
| 31 | + |
| 32 | +- `정렬` |
| 33 | + |
| 34 | + 이분 탐색 알고리즘은 <b>정렬된 배열이나 리스트</b>에서만 적용할 수 있습니다. |
| 35 | + |
| 36 | +- `시간 복잡도(Time Complexity)` |
| 37 | + |
| 38 | + 선형 탐색과 달리, 중간값을 기준으로 탐색 범위를 절반씩 줄여가며 원하는 값을 찾으므로 `O(log N)`의 시간 복잡도를 갖습니다. 이는 시간 복잡도가 `O(N)`인 선형 탐색보다 빠르므로 정렬된 자료 구조에서 원하는 값을 빠르게 찾을 수 있습니다. |
| 39 | + |
| 40 | +### 구현 |
| 41 | + |
| 42 | +다음과 같이 `[2, 3, 5, 7, 11, 13, 17, 21, 23, 29]`이라는 배열이 있을 때, 숫자 25보다 작은 값들 중에서 가장 큰 값을 구하는 문제가 있다고 가정합니다. |
| 43 | + |
| 44 | +```javascript |
| 45 | +const arr = [2, 3, 5, 7, 11, 13, 17, 21, 23, 29]; |
| 46 | +const target = 25; |
| 47 | +``` |
| 48 | + |
| 49 | +배열 arr이 오름차순으로 정렬되어 있으므로 이분 탐색 알고리즘을 적용할 수 있습니다. 다음과 같이 탐색 구간의 왼쪽 경계, 즉 target보다 작은 값의 인덱스 후보를 나타내는 `left(lower bound)`와 탐색 구간의 오른쪽 경계, 즉 target보다 크거나 같은 값의 첫 위치 후보를 나타내는 `right(upper bound)`를 정의합니다. 이 때 이분 탐색에서 `left`와 `right`의 초깃값은 어떤 값을 찾는가에 따라 신중하게 설정해야 합니다. 이번 문제에서는 arr 내에 target보다 작은 값이 없을 수도 있으므로 left의 초깃값을 -1로 설정하였고, 배열의 모든 값이 target보다 작을 수 있으므로 right의 초깃값을 arr.length로 설정하였습니다. |
| 50 | + |
| 51 | +```javascript |
| 52 | +/* ... */ |
| 53 | + |
| 54 | +let left = -1; |
| 55 | +let right = arr.length; |
| 56 | +``` |
| 57 | + |
| 58 | +이후 다음과 같이 `left + 1 < right`로 루프 조건을 설정합니다. 이 조건은 `left`와 `right` 사이에 최소 하나의 값만 남아 있을 때까지만 반복하도록 제한합니다. |
| 59 | + |
| 60 | +```javascript |
| 61 | +/* ... */ |
| 62 | + |
| 63 | +while (left + 1 < right) { |
| 64 | + /* ... */ |
| 65 | +} |
| 66 | +``` |
| 67 | + |
| 68 | +루프 조건을 설정한 이후 탐색 범위를 절반으로 좁히면서 원하는 값을 찾아냅니다. 다음과 같이 탐색 범위를 절반으로 나누기 위해 `mid` 변수를 선언하고 `[left, right)` 구간의 가운데 인덱스 값을 할당합니다. 그리고 `arr[mid]`의 값이 찾고자 하는 값 후보인지 확인한 후 탐색 범위를 좁힙니다. |
| 69 | + |
| 70 | +```javascript |
| 71 | +/* ... */ |
| 72 | + |
| 73 | +while (left + 1 < right) { |
| 74 | + const mid = Math.floor((left + right) / 2); |
| 75 | + |
| 76 | + if (arr[mid] < target) { |
| 77 | + left = mid; |
| 78 | + } else { |
| 79 | + right = mid; |
| 80 | + } |
| 81 | +} |
| 82 | +``` |
| 83 | + |
| 84 | +탐색 이후 찾고자 하는 답이 `left`인지 `right`인지 생각하고 결정합니다. 위의 `arr[mid] < target`이 true인 경우일 때의 mid는 찾고자 하는 답의 후보에 해당하므로 `left`가 찾고자 하는 답임을 확인할 수 있습니다. 따라서 `left`를 선택하여 출력하면 됩니다. |
| 85 | + |
| 86 | +```javascript |
| 87 | +/* ... */ |
| 88 | + |
| 89 | +if (left >= 0) { |
| 90 | + console.log(arr[left]); // 23 |
| 91 | +} else { |
| 92 | + console.log("arr 내에 25보다 작은 값은 없습니다."); |
| 93 | +} |
| 94 | +``` |
| 95 | + |
| 96 | +최종 구현 결과는 다음과 같습니다. |
| 97 | + |
| 98 | +```javascript |
| 99 | +const arr = [2, 3, 5, 7, 11, 13, 17, 21, 23, 29]; |
| 100 | +const target = 25; |
| 101 | +let left = -1; |
| 102 | +let right = arr.length; |
| 103 | + |
| 104 | +while (left + 1 < right) { |
| 105 | + const mid = Math.floor((left + right) / 2); |
| 106 | + |
| 107 | + if (arr[mid] < target) { |
| 108 | + left = mid; |
| 109 | + } else { |
| 110 | + right = mid; |
| 111 | + } |
| 112 | +} |
| 113 | + |
| 114 | +if (left >= 0) { |
| 115 | + console.log(arr[left]); // 23 |
| 116 | +} else { |
| 117 | + console.log("arr 내에 25보다 작은 값은 없습니다."); |
| 118 | +} |
| 119 | +``` |
| 120 | + |
| 121 | +## Example |
| 122 | + |
| 123 | +- <a href="https://www.acmicpc.net/problem/19845" target="_blank">19845번: 넴모넴모 2020</a> |
| 124 | + |
| 125 | + ```javascript |
| 126 | + const path = process.platform === "linux" ? "/dev/stdin" : "input.txt"; |
| 127 | + const input = require("fs").readFileSync(path).toString().split("\n"); |
| 128 | + |
| 129 | + // N: 게임판의 세로 크기, 1 <= N <= 250_000 |
| 130 | + // Q: 레이저를 설치할 수 있는 위치의 수, 1 <= Q <= 250_000 |
| 131 | + const [N, Q] = input[0].split(" ").map(Number); |
| 132 | + const arr = input[1].split(" ").map(Number); // 1 <= arr[i] <= 10^9 |
| 133 | + let answer = ""; |
| 134 | + |
| 135 | + for (let i = 2; i < 2 + Q; i++) { |
| 136 | + const [col, row] = input[i].split(" ").map(Number); |
| 137 | + |
| 138 | + let left = 0; |
| 139 | + let right = N; |
| 140 | + |
| 141 | + while (left + 1 < right) { |
| 142 | + const mid = Math.floor((left + right) / 2); |
| 143 | + |
| 144 | + if (arr[mid] < col) { |
| 145 | + right = mid; |
| 146 | + } else { |
| 147 | + left = mid; |
| 148 | + } |
| 149 | + } |
| 150 | + |
| 151 | + let result = arr[row - 1] - col; |
| 152 | + result += right - row + 1; |
| 153 | + |
| 154 | + if (result < 0) { |
| 155 | + result = 0; |
| 156 | + } |
| 157 | + |
| 158 | + answer += `${result}\n`; |
| 159 | + } |
| 160 | + |
| 161 | + console.log(answer.trim()); |
| 162 | + ``` |
| 163 | + |
| 164 | +## 참고 자료 |
| 165 | + |
| 166 | +- <a href="https://namu.wiki/w/이진%20탐색?from=이분%20탐색" target="_blank">이진 탐색 - 나무위키</a> |
| 167 | +- <a href="https://www.acmicpc.net/blog/view/109" target="_blank">이분 탐색(Binary Search) 헷갈리지 않게 구현하기</a> |
0 commit comments