4week 이진탐색

최효준·2022년 9월 5일
0

AlgorithmStudy

목록 보기
4/9

이진탐색

1. 순차탐색

순차탐색이란 리스트 안에 있는 특정한 데이터를 찾기 위해 앞에서부터 데이터를 하나씩 차례대로 확인하는 방법이다.
시간만 충분하다면 항상 원하는 원소를 찾을 수 있다는 장점이 있다.

순차탐색은 이름 그대로 순차적으로 데이터를 탐색하는데 리스트의 각 데이터에 하나씩 방문하여 특정한 문자열과 같은지 검사하므로 구현도 간단하다. 리스트에서 특정 값을 체크하는 경우에도 사용하고 특정한 값을 가지는 원소의 개수를 세는 count() 메소드의 경우에도 내부적으로 순차탐색을 수행한다.

# 순차탐색 소스코드 구현
def sequential_search(n, target, array):
	#각 원소를 하나씩 확인
   for i in range(n):
   	#현재의 원소가 찾고자 하는 원소와 동일한 경우
   	if array[i] == target:
       	return i+1 #현재 위치 반환
           
print("생성할 원소 개수를 입력한 다음 한 칸 띄고 찾을 문자열을 입력하세요.")
input_data = input().split()
n = int(input_data[0]) #원소의 개수
target = input_data[1] #찾고자 하는 문자열

print("앞서 적은 원소 개수만큼 문자열을 입력하세요. 구분은 띄어쓰기 한 칸으로 한다.")
array = input().split()

# 결과 출력
print(sequential_search(n, target, array))

해당 소스코드를 실행하면 정상적으로 이름 문자열이 몇 번째 데이터인지 출력한다.

이처럼 순차탐색은 데이터 정렬 여부와 상관없이 가장 앞에 있는 원소부터 하나씩 확인해야 한다. 최악의 경우의 시간복잡도는 O(N)이다.

2. 이진탐색

이진 탐색은 배열 내부의 데이터가 이미 정렬 되어있어야 사용할 수 있다.
이미 정렬되어 있는 데이터라면 이진탐색은 매우 빠르게 결과값을 낼 수 있다. 이진 탐색은 범위를 절반씩 좁혀가며 데이터를 탐색하는 특징이 있다.

이진탐색은 위치를 나타내는 변수 3가지를 사용하는데 탐색하고자 하는 범위의 시작점, 끝점, 중간점 이다. 찾으려는 데이터와 중간점 위치에 있는 데이터를 반복적으로 비교해서 원하는 데이터를 찾는게 이진탐색이다.

이진 탐색 알고리즘은 평균적으로 한 단계를 거칠때마다 확인하는 원소의 개수가 절반으로 줄어든다. 이런 이진탐색의 시간복잡도는 O(logN) 이다. 평균적으로 확인해야 되는 원소의 개수가 절반씩 줄어든다는 점에서 퀵정렬과 비슷하다.

이진탐색은 재귀함수 또는 반복문을 사용하여 구현할 수 있다.

# 재귀를 통한 이진 탐색 구현
def binary_search(array, target, start, end):
	if start > end:
    	return None
    mid = (start + end) // 2
    
    # 찾은 경우 중간점 인덱스 반환
    if array[mid] == target:
    	return mid
    # 중간점의 값보다 찾고자 하는 값이 작은 경우 왼쪽 확인
    elif array[mid] > target:
    	return binary_search(array, target, start, mid - 1)
    # 중간점의 값보다 찾고자 하는 값이 큰 경우 오른쪽 확인
    else:
    	return binary_search(array, target, mid + 1, end)
        
# n(원소의 개수)과 target(찾고잦 하는 문자열)을  입력받기
n, target = list(map(int, input().split()))
# 전체 원소 입력 받기
array = list(map(int, input().split()))

# 이진 탐색 수행 결과 출력
result = binary_search(array, target, 0, n-1)
if result == None:
	print("원소는 존재하지 않습니다.")
else:
	print(result + 1)

위의 코드에서 mid 는 중간점을 의미한다. 그렇기에 //를 통해 몫만 구해줬다.

# 반복을 사용한 이진탐색 구현
def binary_search(array, target, start, end):
	while start <= end:
    	mid = (start+end) // 2
        
        # 찾은 경우 중간점 인덱스 반환
        if array[mid] == target:
        	return mid
        # 중간점의 값보다 찾고자 하는 값이 작은 경우 왼쪽 확인
        elif array[mid] > target:
        	end = mid - 1
        # 중간점의 값보다 찾고자 하는 값이 큰 경우 오른쪽 확인
        else:
        	start = mid + 1
        return None
        
# n(원소의 개수)과 target(찾고잦 하는 문자열)을  입력받기
n, target = list(map(int, input().split()))
# 전체 원소 입력 받기
array = list(map(int, input().split()))

# 이진 탐색 수행 결과 출력
result = binary_search(array, target, 0, n-1)
if result == None:
	print("원소는 존재하지 않습니다.")
else:
	print(result + 1)

코딩 테스트에서의 이진 탐색
단순히 앞의 코드를 보고 이진 탐색이 단순하다고 느낄 수 있지만 실제 참고할 소스코드가 없는 상태에서의 이진 탐색을 구현하는 것은 생각보다 까다롭다. 고로 여러번 코드를 읽어보면서 암기하는 것이 좋다. 코딩 테스트에서도 단골로 출제되는 만큼 숙달될 정도로 여러번 볼 것!!!

이진탐색의 원리는 다른 알고리즘에서도 폭넓게 적용되는 원리와 유사하기 때문에 알아두면 다른 알고리즘을 이해하는 데도 좋다. 또 문제의 난이도가 높은 겨우에 이진 탐색 알고리즘이 다른 알고리즘과 함께 이용되는 경우도 있어 암기만 해두어도 도움이 된다.

1000만 단위를 넘어가는 자료 탐색시에는 이진 탐색과 같이 O(logN)의 속도를 내는 알고리즘을 떠올릴 것!

3. 트리 자료구조

이진 탐색은 데이터가 정렬되어 있음을 전제로 실행된다. 동작하는 프로그램에서는 데이터를 정렬해두는 경우가 많아 이진탐색을 효과적으로 사용할 수 있다.
데이터 베이스의 경우에는 내부적으로 대용량 데이터 처리에 적합한 트리 자료구조를 사용하여 항상 데이터가 정렬되어 있는데 이 데이터베이스에서의 탐색은 이진 탐색과는 조금 다르지만 유사한 방법을 이용하여 탐색을 실시한다.

트리 자료구조란?
노드와 노드의 연결로 표현되어 있는 자료구조이며 여기에서 노드는 정보의 단위로서 어떠한 정보를 가지고 있는 개체로 이해할 수 있다. 그래프 자료구조에서의 노드와 동일하다.
이 트리 자료구조는
1.트리는 부모 노드와 자식 노드의 관계로 표현된다.
2.트리의 최상단 노드를 루트 노드라고 한다.
3.트리의 최하단 노드를 단말 노드라고 한다.
4.트리에서 일부를 떼어내도 트리 구조이며 이를 서브트리 라고 한다.
5.트리는 파일 시스템과 같이 계층적이고 정렬된 데이터를 다루기에 적합하다.
라는 특징을 가지고 있다.

대부분의 큰 데이터를 처리하는 소프트웨어에서는 트리 자료구조로 데이터를 저장하고 이진탐색과 같은 기법으로 빠르게 탐색을 실시한다.

4. 이진 탐색 트리

트리 자료구조 중 가장 간단한 형태를 이진 탐색 트리라고 한다. 이진 탐색 트리란 이진 탐색이 동작 할 수 있도록 고안된, 효율적인 탐색이 가능한 자료구조이다.

이진 탐색 트리의 특징
1.부모 노드보다 왼쪽 자식 노드가 작다.
2.부모 노드보다 오른쪽 자식 노드가 크다.
즉, 왼쪽 자식 노드 < 부모 노드 < 오른쪽 자식 노드 가 성립해야 이진 탐색 트리이다.

파이썬을 사용하는 경우 이진 탐색 문제에서 많은 수의 데이터를 입력받아야 할 경우 input()을 사용하면 시간 초과로 오답을 받을 수 있다. 이 경우에는 sys 라이브러리의 readline() 함수를 이용할 것!

import sys

input_data = sys.stdin.readline().rstrip()
.
.
.

sys 라이브러리 사용 시 한 줄을 입력받고 나서 rstrip() 함수를 꼭 호출 할 것! readline() 함수는 입력 후 엔터가 줄 바꿈 기호로 입력이 되기 때문에 이를 없애려면 rstrip() 함수를 사용해야 한다.

예시문제 1 부품찾기
동빈이네 전자 매장에는 부품이 N개 있다. 각 부품은 정수 형태의 고유한 번호가 있다. 어느날 손님이 M개 종류의 부품을 대량으로 구매하겠다고 다량의 견젹서를 요청했다. 이 때 동빈이는 손님이 문의한 M개 종류를 모두 확인해서 견적서를 작성해야 한다. 이 때
가게 안에 부품이 모두 있는지 확인하는 프로그램을 작성해라

총 5개의 부품이 있고 각 부품번호는 다음과 같다.
N=5
[8,3,7,9,2]
손님은 총 3개의 부품이 있는 확인 요청했고 부품 번호는 다음과 같다.
M = 3
[5,7,9]

이때 손님이 요청한 부품 번호의 순서대로 부품을 확인해 부품이 있으면 yes 없으면 no를 출력해라. 구분은 공백으로 한다.

solution

def binary_search(array, target, start, end):
	while start <= end:
    	mid = (start + end) //2
        # 찾은 경우 중간점의 인덱스를 반환한다.
        if array[mid] == target:
        	return mid
        # 중간점의 값보다 찾고자 하는 값이 작은 경우 왼쪽 확인
        elif array[mid] > target:
        	end = mid - 1
        # 중간점의 값보다 찾고자 하는 값이 큰 경우 오른쪽 확인
        else:
        	start = mid + 1
        return None
        
# N(가게의 부품 수) 입력
n = int(input())
# 가게에 있는 전체 부품 번호를 공백으로 구분하여 입력
array = list(map(int, input().split()))
array.sort() # 이진 탐색을 수행하기 위해 사전에 정렬 수행
# M(손님이 확인 요청한 부품 개수)입력
m = int(input())
# 손님이 확인 요청한 전체 부품 번호를 공백으로 구분하여 입력
x = list(map(int, input().split()))

# 손님이 확인 요청한 부품 번호를 하나씩 확인
for i in x:
	# 해당 부품이 존재하는지 확인
    result = binary_search(array, i, 0, n-1)
    if result != None:
    	print('yes', end=' ')
    else:
    	print('no', end=' ')

예시문제 2 떡볶이 떡 만들기
동빈이는 떡볶이 떡을 만들려고 한다. 각 떡의 길이는 일정하지 않지만 한 봉지 안에 들어가는 떡의 총 길이는 절단기로 잘라서 맞춘다.
절단기에 높이(H)를 지정하면 줄지어진 떡을 한 번에 절단한다. 높이가 H보다 긴 떡은 H 위의 부분이 잘릴 것이고 낮은 떡은 잘리지 않는다.
예를 들어 높이가 19, 14, 10, 17cm인 떡이 나란히 있고 절단기 높이를 15cm로 지정하면 자른 뒤 떡의 높이는 15, 14, 10, 15cm가 될 것이다. 잘린 떡의 길이는 차례대로 4, 0, 0, 2cm 이다. 손님은 6cm만큼의 길이를 가져간다.
손님이 왔을 때 요청한 총 길이가 M일때 적어도 M만큼의 떡을 얻기 위해 절단기에 설정할 수 있는 높이의 최댓값을 구해라


입력예시
4 6
19 15 10 17
출력 예시
15

위 문제는 전형적인 '파라메트릭 서치' 유형의 문제이다.
파라메트릭 서치란 최적화 문제를 결정 문제로 바꾸어 해결하는 기법인데 여기서 결정 문제는 yes or no 로 답하는 문제를 말한다.
코딩 테스트에서는 보통 파라메트릭 서치 유형을 이진 탐색을 이용하여 해결한다.

solution

# 떡의 개수(N)와 요청한 떡의 길이(M)을 입력받기
n, m = list(map(int, input().split(' ')))
# 각 떡의 개별 높이 정보를 입력받기
array = list(map(int, input().split()))

# 이진 탐색을 위한 시작점과 끝점 설정
start = 0
end = max(array)

# 이진 탐색 수행(반복)
result = 0
while(start <= end):
	total = 0
    mid = (start + end) // 2
    for x in array:
    	# 잘랐을 때의 떡의 양 계산
        if x > mid:
        	total += x - mid
    # 떡의 양이 부족한 경우 더 많이 자르기(왼쪽 부분 탐색)
    if total < m:
    	end = mid -1
    # 떡의 양이 충분한 경우 덜 자르기(오른쪽 부분 탐색)
    else:
    	result = mid # 최대한 덜 잘랐을 때가 정답이므로, 여기에서 result에 기록
        start = mid + 1
        
# 정답 출력
print(result)

출처
이것이 취업을 위한 코딩 테스트다 with 파이썬
저자 나동빈

이 글은 위 서적을 바탕으로 학습하기 위해 작성되었습니다.

profile
Not to be Number One, but to be Only One

0개의 댓글