주어진 리스트에서 특정 데이터를 찾으려고 한다. 가장 쉬운 방법은 원소를 앞에서부터 확인하면서 일치하는지 확인하는 것이다. 리스트의 크기가 일 때, 의 시간복잡도를 가진다.
우리는 통상 코딩 테스트를 볼 때 시간복잡도를 고려하여 알고리즘을 짜야한다. 언어에 따라 다르지만, 통상 1초에 1억번 연산이 가능하다고 한다.
이때 이 엄청나게 커지면 어떻게 될까? 선형탐색은 매우 비효율적일 것이다.
그래서 우리는 더 효율적인 탐색 기법이 필요하다. 이때 사용하는 것이 이진탐색이다.
이진탐색이란, 매 탐색마다 탐색 범위를 반으로 좁혀가며 빠르게 탐색하는 알고리즘이다.
하지만 이진탐색을 수행하기 위한 전제조건이 있다.
✅ 리스트(배열)가 정렬되어 있어야 한다.
크기가 N
인 list
에서 target
이라는 특정 데이터를 찾아내고 싶다고 가정하자.
(left
와 right
는 list
의 인덱스를 의미한다.)
left = 0
, right = n - 1
로 초기화한다.mid = (left + right) // 2
로 설정한다.list[mid] == target
이라면 탐색을 종료한다.list[mid] != target
이라면,list[mid] > target
: right = mid - 1
로 갱신하고 2번으로 돌아간다.list[mid] < target
: left = mid + 1
로 갱신하고 2번으로 돌아간다.✅ mid
를 설정할 때 left + (right - left) // 2
로 설정하기도 한다.
(오버플로우를 피하기 위함, 단 언어에 따라 다를 수 있음?)
이를 그림으로 살펴보자. 아래에서 15라는 데이터를 찾고자 한다.
left
와 right
로 mid
를 설정하자.
중간값이 15보다 작으므로, left
를 갱신해야 한다.
left
를 갱신하고 mid
를 새로 설정했다.
이번에는 중간값이 15보다 크므로, right
를 갱신해야 한다.
원하는 데이터 15를 찾았다.
이진탐색은 한번 탐색을 수행할 때 마다, 탐색의 범위가 반으로 줄어든다.
리스트의 크기를 이라 하고, 반복 횟수를 k라고 한다면 다음과 같은 수식이 만들어진다.
→
→
→
따라서, 시간복잡도는 이다.
def binary_search(list, target, left, right):
if left > right:
return None
mid = left + (right - left) // 2
# 찾은 경우 중간점 인덱스 변환
if list[mid] == target:
return mid
# 중간점의 값보다 찾고자 하는 값이 작은 경우 왼쪽 확인
elif list[mid] > target:
return binary_search(list, target, left, mid-1)
# 중간점의 값보다 찾고자 하는 값이 큰 경우 오른쪽 확인
else:
return binary_search(list, target, mid+1, right)
def binary_search(list, target, left, right):
while left <= right:
mid = left + (right - left) // 2
if list[mid] == target:
return mid
elif list[mid] > target:
right = mid - 1
else:
left = mid + 1
return None
최단경로 알고리즘이란, 가장 짧은 경로를 찾는 알고리즘이다.
다익스트라, 플로이드 워셜, 벨만 포드를 사용한다.
다음과 같은 문제의 종류에서 사용할 수 있다.
import heapq
import sys
input = sys.stdin.readline
INF = int(1e9)
n, m = map(int, input().split()) # 노드 개수, 간선 개수
start = int(input()) # 시작 노드 번호
grpah = [[] for i in range(n+1)] # 그래프
distance = [INF] * (n+1) # 최단 거리 테이블
# 그래프 입력 받기
for _ in range(m):
a, b, c = map(int, input().split())
graph[a].append((b, c)) # a-> b 가는 비용이 c
def dijkstra(start):
q = []
# 시작 노드로 가기 위한 최단 경로는 0으로 설정한 후 큐에 삽입
heapq.heappush(q, (0, start))
distance[start] = 0
while q:
# 가장 최단 거리가 짧은 노드에 대한 정보 꺼내기
dist, now = heapq.heapop(q)
# 현재 노드가 이미 처리된 적 있는 노드라면 무시
if distance[now] < dist:
continue
# 현재 노드와 연결된 다른 인접한 노드들 확인
for i in graph[now]:
cost = dist + i[1]
# 현재 노드 거쳐서 다른 노드로 이동하는 거리가 더 짧은 경우
if cost < distance[i[0]]:
distance[i[0]] = cost
heapq.heappush(q, (cost, i[0]))
dijkstra(start) # 다익스트라 수행
정점의 개수가 , 간선의 개수가 일 때,
단계마다 모든 노드를 확인하는 방식으로 구현한다면 이다.
하지만, 대부분 다익스트라를 사용할 때 힙 자료구조를 이용하여 구현한다.
이때는 이다.
플로이드 워셜 알고리즘은 각 단계마다 특정한 노드 k를 거치는 경우를 확인한다.
즉, a → b
보다 a → k → b
가 더 짧은지 검사를 진행한다.
이를 수식으로 나타내면 다음과 같다.
INF = int(1e9)
n, m = map(int, input().split()) # 노드 개수, 간선 개수
# 그래프 초기화
graph = [[INF]* (n+1) for _ in range(n+1)]
for a in range(1, n+1):
for b in range(1, n+1):
if a == b :
graph[a][b] = 0
# 그래프 입력 받기
for _ in range(m):
a, b, c = map(int, input().split())
graph[a][b] = c # a-> b 비용은 c
# 점화식 따라 플로드 워셜 알고리즘 수행
for k in range(1, n+1): # 경유지
for a in range(1, n+1): # 출발지
for b in range(1, n+1): # 도착지
graph[a][b] = min(graph[a][b], graph[a][k]+graph[k][b])
노드의 개수가 개 일 때, 번의 단계(k 노드를 거치는 단계)를 수행한다.
각 단계마다 의 연산을 통해 현재 노드를 거쳐 가는 모든 경로를 고려한다.
따라서, 시간복잡도는 이다.
최대공약수는 GCD
, 최소공배수는 LCM
이라고도 불리며 손코딩으로 많이 출제되는 유형이다.
최대공약수는 유클리드 호제법을 사용해서 구현할 수 있고, 최소 공배수는 최대공약수 알고리즘을 사용하여 구현할 수 있다.
유클리드 호제법이란,
2개의 자연수 ,에 대해서 를 로 나눈 나머지를 이라고 할 때 (단, ), 와 의 최대공약수는 와 의 최대공약수와 같다.
이 성질에 따라, 를 로 나눈 나머지 를 구하고 을 으로 나눈 나머지를 구하는 과정을 반복하여 나머지가 0이 되었을 때 나누는 수가 와 의 최대공약수이다.
수식으로 표현하면 다음과 같다.
def gcd(a, b):
while b > 0:
a, b = b, a % b
return a
def gcd(a, b):
if a % b == 0:
return b
elif b == 0:
return a
else:
return gcd(b, a % b)
최소공배수란, 서로 다른 수 , 의 배수 중에서 공통되는 배수 중에 가장 작은 값을 의미한다.
와 의 최소공배수는 와 의 곱을 와 의 최대 공약수로 나누면 된다.
def lcm(a, b):
return a * b / gcd(a, b)
일반적으로 소수 찾는 알고리즘을 하드코딩하면 다음과 같다.
def isPrime(n) {
result = 1
for i in range(3, n + 1):
check = 0
for j in range(2, i + 1):
if i % j == 0 and i != j:
check = 0
break
elif i % j != 0:
check = j
if check:
result += 1
return result;
3부터 시작하여 소수를 판별하는 코드이다.
이 코드는 n
이 일 때 5~6초가 소요된다. 효율성이 매우 떨어지기에, 다른 코드를 생각해야 한다.
에라토스테네스의 진행방식은 다음과 같다.
a = [False, False] + [True] * (n-1)
primes=[]
def eratosthenes():
for i in range(2,n+1):
if a[i]:
for j in range(2*i, n+1, i):
a[j] = False
여기서 True
는 소수를 의미하고, False
는 소수가 아님을 의미한다.
재귀란 자기 자신을 계속해서 호출하는 것을 의미한다.
반복과 재귀의 차이를 살펴보자.
반복문 | 재귀함수 | |
---|---|---|
동작 | 명령을 반복적으로 실행 | 함수 자체를 호출 |
체제 | 초기화, 조건, 루프 내 명령문 실행과 제어 변수 업데이트 포함 | 종료 조건(기저조건, Basis)를 지정(조건이 추가될 수 있음) |
종료시점 | 설정한 조건에 도달 할 때까지 반복 실행 | 함수 호출 본문에 조건부가 포함, 재귀를 호출하지 않고 함수를 강제 반환 |
조건 | 제어 조건이 참이라면 무한 반복 발생 | 조건에 수렴하지 않을 경우 무한 재귀 발생 |
무한 반복 | 무한 루프는 CPU 사이클을 반복적으로 사용 | 무한 재귀는 스택 오버플로우 발생 |
스택 메모리 | 스택 메모리를 사용하지 않음 | 함수가 호출 될 때마다 새 로컬 변수와 매개 변수 집합, 함수 호출 위치를 저장하는데 사용 |
속도 | 빠른 실행 | 느린 실행 |
가독성 | 코드 길이가 길어지고 변수가 많아져 가독성이 떨어짐 | 코드 길이와 변수가 적어 가독성이 높아짐 |
재귀함수는 stack
이라는 메모리 공간을 사용한다.
반복적인 재귀 호출을 통해 stack
에 자기 자신이 계속해서 쌓여가기 때문에 성능적인 측면에서 좋지 않다.
재귀함수는 stack
이라는 메모리 공간을 사용하기 때문에, 무한 재귀가 발생할 경우 메모리의 제한이 있는 한 Stack Overflow
가 발생하면서 프로그램이 비정상 종료된다.
Memoization
기법을 사용한다. dp = [0] * n
dp[0] = 0
dp[1] = 1
for i in range(2, n + 1): // 2부터 시작해서 n까지 반복
dp[i] = dp[i-1] + dp[i-2]
DP
)Memoization
기법을 사용한다.Memoization
기법을 사용하지 않는다.그리디(a.k.a 탐욕법
) 알고리즘이란, 각 단계에서 가장 최선의 선택을 하는 알고리즘이다.
즉, 현재 상황에서 가장 좋은 결과를 선택해 나가는 방식이고 최적해를 구하는 데에 사용되는 근사적인 방법이다.
하지만, 순간마다 하는 선택은 그 순간에 대해 지역적으로는 최적이지만 그 선택들을 계속 수집하여 최종적(전역적)인 해답을 만들었다고 해서, 그것이 최적이라는 보장은 없다.
그러나 그리디 알고리즘을 적용할 수 있는 문제들은 지역적으로 최적이면서 전역적으로 최적인 문제들이다.
그리디 알고리즘으로 풀 수 있는 문제는 대부분 탐욕스런 선택 조건(greedy choice property
)과 최적 부분 구조 조건(optimal substructure
)이라는 두 가지 조건이 만족된다.
백트래킹(Backtracking
)이란, 해를 찾는 도중 해가 아니어서 막히게되면 그 경로를 더 이상 탐색하지 않고, 되돌아가서 다시 해를 찾아가는 기법이다.
백트래킹은 다음과 같이 설명할 수 있다.
가지치기란?
해가 될 것 같지 않으면 멈추고 다른 경로를 탐색하는 것을 의미한다.
Huffman 코딩
이란,
데이터 압축을 수행하는 알고리즘으로, 그리디 알고리즘에 속한다.
문자의 빈도 또는 확률 정보를 이용해 통계적 압축을 진행하는 기법이다.
고정 길이 코드
와 가변 길이 코드
, 두 가지 표현 방법이 존재한다.
[참고&출처]
https://github.com/songhee-lee/2023-python-coding-testhttps://myjamong.tistory.com/138
https://jina-developer.tistory.com/118
https://tecoble.techcourse.co.kr/post/2020-04-30-iteration_vs_recursion/
https://velog.io/@gillog/Algorithm-%EC%9E%AC%EA%B7%80%EC%99%80-%EB%B0%98%EB%B3%B5%EB%AC%B8
https://velog.io/@junhok82/%ED%97%88%ED%94%84%EB%A7%8C-%EC%BD%94%EB%94%A9Huffman-coding