“데이터를 검색하는 방법”
문제 → 과정 → 리턴
1) a[pc]와 key 값을 비교한다.
2) a[pc] < key
이면 → pl = pc + 1 / pr = pr → 왼쪽 버리고 → pc + 1 ~ pr
에서 찾기! a[pc] > key
이면 → pl = pl / pr = pc - 1 → 오른쪽 버리고 → pl ~ pc - 1
에서 찾기!
3) a[pc] == key
가 될 때까지 2번 과정을 반복한다.
(혹은 검색범위가 더 이상 없는 경우, 끝난다.)
1) 반복문
from typing import Any, Sequence
# 이진 검색 알고리즘
def bin_search(a: Sequence, key: Any) -> int:
"""시퀀스 a에서 key와 일치하는 원소를 이진 검색"""
pl = 0 # 검색 범위 맨 앞 원소의 인덱스
pr = len(a) - 1 # 검색 범위 맨 끝 원소의 인덱스
while True:
pc = (pl + pr) // 2 # 중앙 원소의 인덱스
if a[pc] == key:
return pc # 검색 성공
elif a[pc] < key:
pl = pc + 1 # 검색 범위를 뒤쪽 절반으로 좁힘
else:
pr = pc - 1 # 검색 범위를 앞쪽 절반으로 좁힘
if pl > pr: # key가 없음 (검색 실패)
return -1
2) 재귀함수
from typing import Any, Sequence
# 이진 검색 알고리즘
def bin_search(a: Sequence, key: Any, pl: int, pr: int) -> int:
pc = (pl + pr) // 2
if pl > pr: # key가 없음 (검색 실패)
return -1
if a[pc] == key:
return pc # 검색 성공
elif a[pc] < key:
return bin_search(a, key, pc + 1, pr) # 검색 범위를 뒤쪽 절반으로 좁힘
else:
return bin_search(a, key, pl, pc - 1) # 검색 범위를 앞쪽 절반으로 좁힘
※ 이미 (오름차순으로) 정렬된 리스트에 한해서 가능하다. (sort 함수 사용 or 입력 시 이전 원소보다 크게끔 설정)
⭐ 시간복잡도는 O(logN)
"..응? 쉬운데?"라는 생각이 들었다면
이진탐색을 왜 공부해야 하는지!
바로 이진탐색트리의 핵심 알고리즘이기 때문이다. (라고 생각한다.)
이진탐색트리 (BST)
"데이터 사이의 계층 관계를 나타내는 자료구조 중 하나"이다.
일상에서도 ‘트리’를 접할 수 있다. 쉽게! (우리 노트북의 파일들도 '트리' 구조이다.)
추..가로 ‘그래프’도 있는데 ‘트리’의 부분집합이다. 사이클이 있고 없고의 차이 → 외판원순회문제
이 이상은..다음 주차 때! 커밍쑨
파이썬에서는 찾고자 하는 원소의 인덱스를 반환하는 함수가 있다.
: obj.index(x, i, j)
→ 리스트 또는 튜플 obj[i:j]
안에 x와 값이 같은 원소가 있으면 그 가운데 가장 작은 인덱스를 반환한다. (없으면 예외처리로 ValueError)
“알고리즘 설계 기법 & 전략”
이분탐색..분할정복..뭐가 뭐야?
f(n, val) = f(n/2, val)
f(n) = f(n/2) + f(n/2) + O(n)
분할 정복(Divide and Conquer)은 여러 알고리즘의 기본이 되는 해결방법으로,
기본적으로는 엄청나게 크고 방대한 문제를 조금씩 조금씩 나눠가면서 용이하게 풀 수 있는 문제 단위로 나눈 다음 그것들을 다시 합쳐서 해결하자는 개념에서 출발하였다.
대표적으로는 퀵소트나 병합정렬이 있다.
(출처 : 나무위키)
“상단에서 분할하고 중앙에서 정복하고 하단에서 조합한다.”
1) 분할 : 문제를 더이상 분할할 수 없을 때까지 동일한 유형의 여러 하위 문제로 나눈다.
2) 정복 : 가장 작은 단위의 하위 문제를 해결하여 정복한다.
3) 조합 : 하위 문제에 대한 결과를 원래 문제에 대한 결과로 조합한다.
def F(x):
if F(x)가 간단 then:
return F(x)를 계산한 값
else:
x를 x1, x2로 분할
F(x1)과 F(x2)를 호출
return F(x1), F(x2)로 구한 결과값 F(x)
재귀적으로 자신을 호출하면서 그 연산의 단위를 조금씩 줄여간다.
▶ 즉, “F(x)가 간단”이라는 조건을 만족할 때까지 문제를 쪼개고 쪼개서 값을 구하자!
1) Merge Sort (병합 정렬)
Divide : 정 가운데 원소를 기준으로 재귀적으로 좌우를 분할힌다.
Conquer : 왼쪽의 리스트와 오른쪽의 리스트를 각각 재귀적으로 병합 정렬
Obtain : 정렬된 리스트를 리턴
def mergeSort(S, low, high):
if (low < high):
mid = (low + high) // 2
mergeSort(S, low, mid)
mergeSort(S, mid + 1, high)
merge2(S, low, mid, high)
def merge2(S, low, mid, high):
u = []
i = low
j = mid + 1
while (i<= mid and j <= high):
if(S[i] < S[j]):
u.append(S[i])
i += 1
else:
u.append(S[j])
j += 1
if (i <= mid):
u += S[i : mid + 1]
else:
u += S[j : high + 1]
for k in range(low, high + 1):
S[k] = u[k - low]
2) Quick Sort (퀵 정렬)
Divide : 기준 원소(pivot)을 정해서 기준 원소를 기준으로 좌우로 분할
Conquer : 왼쪽의 리스트와 오른쪽의 리스트를 각각 재귀적으로 퀵 정렬
Obtain : 정렬된 리스트를 리턴
def quickSort(S, low, high):
if(high > low):
pivotpoint = partition(S, low, high)
quickSort(S, low, pivotpoint - 1)
quickSort(S,pivotpoint + 1, high)
def partition (S, low, high):
pivotitem = S[low]
i = low + 1
j = high
while (i <= j):
while (S[i] < pivotitem):
i += 1
while (S[j] > pivotitem):
j -= 1
if (i < j):
S[i], S[j] = S[j], S[i]
pivotpoint = j
S[low], S[pivotpoint] = S[pivotpoint], S[low]
return pivotpoint
장점
단점
※ 잠깐! 오버헤드란?
: 프로그램의 실행 흐름 도중 동떨어진 위치의 코드를 실행시켜야 할 때,
추가적으로 시간/메모리/자원이 사용되는 현상
(줄일 수 있는 방법 : 매크로 함수, 인라인 함수로 최적화 시킬 수 있다.)
⭐시간복잡도는 O(NlogN)