이진 탐색(Binary Search)
데이터가 정렬되어 있을 때, 선형 탐색(Linear Search)보다 더 빠른 O(logN)의 시간이 걸리는 탐색 알고리즘이다.
(알고리즘에서 log는 밑이 2인 log2를 말하는 것이다.)
이진 탐색은 UP/DOWN으로 숫자를 맞추는 게임이랑 똑같다고 볼 수 있다.
만약 1~100의 숫자에서 65라는 숫자를 골랐을 경우를 가정하자
65를 빨리 맞출 수 있는 방법은 아래와 같이 5번의 질문으로 끝낼 수 있다.
(1+100)/2 = 50?
UP
(50+100)/2 = 75?
DOWN
(50+75)/2 = 62?
UP
(62+75)/2 = 68?
DOWN
(62+68)/2 = 65?
CORRECT
이 방법은 현재 남은 범위에서 최소값과 최대값의 합에 2를 나누어서 나오는 몫을 물어보며 범위를 갱신하는 방법인데, 이진 탐색도 데이터가 정렬된 상태에서 탐색하기 때문에 이런 방법을 사용하여 값을 찾는다.
이 과정을 코드로 만들어야하니 번호를 붙여 설명하자면
1. low와 high라는 변수로 범위를 설정
2. mid라는 변수를 통해 가운데 값을 계산한다.
3-1. mid 값과 찾는 값이 같으면 루프를 빠져나온다.
3-2. val > mid이면 low = mid + 1로 변경, val < mid이면 high = mid - 1로 변경한다.
4. low > high가 될때까지 2~4를 반복한다.
라고 말할 수 있다.
이것을 코드로 구현하면
C++
#include <bits/stdc++.h>
using namespace std;
int main()
{
int num[101];
int low=1, high=100, mid, val, res = 0;
cout << "찾는 값을 입력해주세요:";
cin >> val;
for(int i = 1; i <= 100; i++)
num[i] = i;
while(low <= high)
{
mid = (low + high) / 2;
res += 1;
cout << mid << endl;
if (num[mid] == val)
break;
if (num[mid] > val)
high = mid - 1;
if (num[mid] < val)
low = mid + 1;
}
cout << res << "번만에 값을 찾았습니다." << endl;
return 0;
}
Python
num = [i for i in range(101)]
low, high = 1, 100
res = 0
val = int(input("찾는 값을 입력해주세요: "))
while low <= high:
mid = (low + high) // 2
res += 1
print(mid)
if num[mid] == val : break
elif num[mid] > val : high = mid - 1
else : low = mid + 1
print(str(res)+"번만에 값을 찾았습니다.")
참고로 이진 탐색은 데이터가 정렬되어 있으니 이것을 그래프로 그려보면 단조증가/단조감소하는 그래프가 나온다.
그리고 삼진 탐색이라는 알고리즘도 있는데 이것은 고등학교 수학에서 배우는 극대값, 극소값 개념이 나온다.
삼진 탐색의 시간복잡도는 O(log3N)으로 이진 탐색보다 빠른 것 같지만 실제로 사용해보면 이진 탐색보다 느리다고 한다.
이진 탐색은 나중에 자료구조 시간에서 이진 탐색 트리라는 곳에서도 사용이 된다.
문제를 통해 이진 탐색을 적용해보자
1920번: 수 찾기
첫째 줄에 자연수 N(1≤N≤100,000)이 주어진다. 다음 줄에는 N개의 정수 A[1], A[2], …, A[N]이 주어진다. 다음 줄에는 M(1≤M≤100,000)이 주어진다. 다음 줄에는 M개의 수들이 주어지는데, 이 수들이 A안��
www.acmicpc.net
이 문제는 먼저 배열 A를 정렬하고, 나중에 입력 받는 배열을 B라고 할 때 for문으로 배열 B의 값을 하나하나 선택하고, 이진 탐색을 하여 배열 B에서 선택한 값이 정렬된 배열 A에 있는지 확인하면 된다.
여기서 시간복잡도는 stl::sort 함수를 사용하는데 이것은 Intro sort이므로 시간복잡도는 O(NlogN), 그리고 선형 탐색을 하는동안 이진 탐색을 하는 코드 역시 O(NlogN)이 걸려서 최악의 경우 O(NlogN)이다.
log2(100,000)의 값은 약 16.6이기 때문에 1억을 넘지 않아서 1초내로 풀 수 있다.
이때 주의할 점은 Fast io를 사용하지 않으면 시간 초과가 나오기 때문에 C++은 cin, cout, endl 대신 scanf, printf, "\n"을 사용하고, Python은 sys.stdin.readline을 사용한다.
C++ 코드
#include <bits/stdc++.h>
using namespace std;
int main()
{
int n,m,A[100001],B[100001];
int low, high, mid, val, res = 0;
scanf("%d", &n);
for(int i = 0; i < n; i++)
scanf("%d", &A[i]);
scanf("%d", &m);
for(int i = 0; i < m; i++)
scanf("%d", &B[i]);
sort(A, A+n);
for(int i = 0; i < m; i++)
{
low = 0;
high = n-1;
val = B[i];
res = 0;
while(low <= high)
{
mid = (low + high) / 2;
if (A[mid] == val)
{
res = 1;
break;
}
if (A[mid] > val)
high = mid - 1;
if (A[mid] < val)
low = mid + 1;
}
cout << res << "\n";
}
return 0;
}
Python 코드
input = __import__('sys').stdin.readline
n = int(input())
A = sorted([*map(int,input().split())])
m = int(input())
B = [*map(int,input().split())]
for i in range(m):
low, high = 0, n-1
val = B[i]
res = 0
while low <= high:
mid = (low + high) // 2
if A[mid] == val:
res = 1
break
if A[mid] > val: high = mid - 1
else: low = mid + 1
print(res)