1. 이진 탐색
- 정렬된 데이터에서 특정 값을 찾는 알고리즘
- 탐색 범위를 반으로 줄여가며 찾기 떄문에 O(lgN) 으로 빠르게 동작한다.
- 만약 정렬된 데이터에 찾고자하는 수가 중복으로 있다면 가장 왼쪽의 원소를 찾는 방법을 lower bound 라 하며, 가장 오른쪽 원소의 다음 위치를 반환하는 방법을 upper bound라고 한다.
- 수열 {1, 2, 3, 3, 3, 4} 에서 3의 lower bound는 인덱스 2, uppder bound는 5다.
- 비슷하게 데이터를 반으로 분할하는 알고리즘으로 분할정복이 있다.
- 하지만 분할정복은 문제를 부분적으로 해결하는 알고리즘으로 탐색이 목적이 아니다.
2. 탐색 과정
- 탐색 범위 low ~ high를 정하고 low <= high 조건을 만족하는 동안 루프를 수행
- 루프에서 탐색 범위가 low > high 될 때까지 범위를 줄여나간다.
- 탐색 범위 low ~ high의 배열의 중간 값 mid = (low + high)/2를 구한다.
- 중간 인덱스 값 mid와 key 값을 비교한다. (key는 탐색하고자 하는 값)
- 중간 값이 key보다 크다면 key는 중간값보다 왼쪽 영역에 존재,
중간 값이 key 보다 작다면 key는 오른쪽 영역에 존재한다.
- 왼쪽 탐색 시 범위 수정: high = mid - 1
- 오른쪽 탐색 시 범위 수정: low = mid + 1
- 이 과정을 반복하며 key 값이 있을 수 있는 범위를 줄여나간다.
- key 값이 중간 인덱스 값과 같다면 탐색 종료!
구현
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
static int binarySearch(int key) {
int low = 0;
int high = arr.length-1;
while(low <= high) {
int mid = (low+high)/2;
if(key == arr[mid]) {
return mid;
}else if(key < arr[mid]){
high = mid - 1;
}else {
low = mid + 1;
}
}
// key가 없다.
return -1;
}
|
cs |
3. 수열에 중복 key 탐색
- 이진 검색해서 중복된 Key가 있을때, upper_bound 또는 lower_bound 를 기준으로 탐색할 수 있다.
- upper_bound
- 목표 값 이상의 값을 처음으로 만나는 위치
- lower_bound
- 찾고자 하는 값 초과한 값을 처음 만나는 위치
- 위 그림에서 key 3의 lower bound는 4번째, upper bound는 7번째가 된다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
|
/*
* key이상 값이 처음으로 나오는 index 구하기
* key가 모든 값보다 크다면 배열 길이가 리턴. 따라서, high 초기화가 기본 이진 탐색과 다르다.
*/
static int getLowerBound(int key) {
int low = 0 ;
int high = arr.length;
while(low < high) {
int mid = (low + high) / 2;
if(key <= arr[mid]) {
// 처음으로 key와 동일하거나 이상을 찾아야 한다 = mid를 포함하여 범위를 줄인다.
high = mid;
}else {
// mid값이 key보다 작기 때문에 범위에 key는 포함되지 않는다.
// 따라서 low를 mid+1로 범위 줄여 다음 mid 값을 증가시킨다. low = mid + 1;
}
}
// low와 high동일해서 아무거나 리턴
return low;
}
/*
* key보다 큰 값이 처음으로 나오는 index 구하기
* key가 모든 값보다 크면 배열 길이 리턴
*/
static int getUpperBound(int key) {
int low = 0;
int high = arr.length;
while(low < high) {
int mid = (low + high) / 2;
if(key < arr[mid]) {
// 처음으로 key보다 큰 값을 찾아야 한다.
// key와 mid 값이 동일한 경우를 제외하여 범위를 줄인다. high = mid;
}else {
// mid값은 key보다 작기 때문에 절대 포함되지 않는다. 따라서, mid+1로 범위 줄인다.
low = mid + 1;
}
}
// low와 high동일해서 아무거나 리턴해도된다.
return low;
}
|
cs |
|
|
구현하는데 헷갈렸던 점
1) key <= mid값 일 때, high를 줄이는 것
mid값을 '예상 key값'으로 이해하면, mid값이 key값보다 크면, high를 mid로 줄여 key가 있을 수 있는 범위를 줄인다.
2) 만약 범위에 속하는 key가 존재하지 않는 경우도 주의하기
key값이 존재하지 않으면,
- 배열 길이 값(범위 초과)이 리턴되거나 ( lower + upper )
- key보다 큰 값의 위치가 리턴된다. 이상을 찾는 lower의미가 달라짐 (lower bound)
따라서, 배열[key] 값과 배열[리턴된 위치의 값]이 일치하는지 확인하여 lower_bound가 제대로 작동하는지 알 수 있다.
참고 및 출처
https://jackpot53.tistory.com/33?category=715474
http://bajamircea.github.io/coding/cpp/2018/08/09/lower-bound.html
'CS > Algorithm' 카테고리의 다른 글
그래프 탐색 알고리즘 - DFS 와 BFS 비교 (0) | 2024.05.15 |
---|---|
그래프 탐색의 인접행렬과 인접리스트 비교 (0) | 2024.05.15 |
투 포인터 (0) | 2023.05.06 |
다이나믹 프로그래밍 (0) | 2021.01.17 |
배열 회전 (0) | 2020.05.03 |