여러 경우 중 하나를 결정해야 할 때마다, 매순간 최적이라고 생각되는 경우를 선택하는 방식으로 진행해서, 최종적인 값을 구하는 방식
총체적인 고려보단, 현재 활용하는 것에서 최적을 선택
탐욕 알고리즘 예
예시1: 동전 문제
지불해야 하는 값이 4720원 일 때 1원, 50원, 100원, 500원 짜리 동전으로 가장 적은 동전의 수로 값을 지불하는 경우
가장 금액이 큰 동전부터 최대한 지불해서 값을 채우는 방식으로 구현 가능
500원 짜리로 4500원까지 채우기 -> 9개
100원 짜리로 4700원까지 채우기 -> 2개
1원 짜리로 4720원까지 채우기 -> 20개
Total -> 31개
탐욕 알고리즘을 활용하여 매순간 최적의 경우를 선택
coin_list = [500, 100, 50, 1]
def min_coin_count(value, coin_list):
total_coin_count = 0
details = list()
coin_list.sort(reverse=True) # reverse 옵션을 통해서 내림차순으로 정렬할 수 있음
for coin in coin_list:
# 큰 값의 동전으로 최대한 해당 비교 금액을 채워야 함으로,몫을 구해서 동전의 개수를 구함
coin_num = value // coin
total_coin_count += coin_num # 전체 코인 개수를 해당 동전 개수 만큼 올림
value -= coin_num * coin # 비교 금액에서 방금 채운 금액 만큼 뺌
details.append([coin, coin_num]) # 더 자세히 알아보기 위해서 해당 턴의 코인 금액과 개수를 기록
return total_coin_count
예시2: 부분 배낭 문제 (Fractional Knapsack Problem)
무개 제한이 k인 배낭에 최대 가치를 가지도록 물건을 넣는 문제
각 물건은 무게(W)와 가치(V)로 표현될 수 있음
물건을 쪼개서 물건의 일부분을 배낭에 넣을 수 있음
물건
1
2
3
4
5
무게
10
15
20
25
30
가치
10
12
10
8
5
무게당 가치 : 1, 0.8, 0.5, 0.32, 0.1666..
무게당 가치가 큰 것을 우선적으로 선택하여 넣으면 됨(탐욕 알고리즘)
# data_list 만들기
data_list = [(10, 10), (15, 12), (20, 10), (25, 8), (30, 5)]
data_list = sorted(data_list, key=lambda x: x[1]/x[0], reverse=True)
print(data_list)
# [(10, 10), (15, 12), (20, 10), (25, 8), (30, 5)]
# 최대 가치 갖게 하는 함수 구현
def get_max_value(data_list, capacity):
data_list = sorted(data_list, key=lambda x: x[1]/x[0], reverse=True)
total_value = 0
data_capacity = 0
details = list()
for data in data_list:
if capacity >= data[0]: # 저장 공간이 남거나 딱 맞는 경우
total_value += data[1]
capacity -= data[0]
details.append([data[0], data[1], 1]) # [해당 품목 무게, 가치, 넣은 비율]
else: # 저장 공간이 부족한 경우(일부분 저장 필요)
fraction = capacity/data[0]]
total_value += data[1] * fraction
capacity -= data[0] * fraction
details.append([data[0], data[1], fraction])
break
return total_value, details
# TEST
get_max_value(data_list, 30)
# (24.5, [[10, 10, 1], [15, 12, 1], [20, 10, 0.25]])
get_max_value(data_list, 101)
# (45, [[10, 10, 1], [15, 12, 1], [20, 10, 1], [25, 8, 1], [30, 5, 1]])
get_max_value(data_list, 102)
# (45, [[10, 10, 1], [15, 12, 1], [20, 10, 1], [25, 8, 1], [30, 5, 1]])
# 어차피 베닝 공간은 넣으려는 물건들의 총 무게 보다 크면 배낭에 무조건 들어가고, 공간이 남는 것이니 모두 넣은것이 최대 가치로 나옴 (해당 data_list에서는 100 이상은 항상 45의 가치가 나옴)
탐욕 알고리즘의 한계
탐욕 알고리즘은 근사치 추정에 활용 (해에 대한 정확성을 보장해 주진 않음)
항상 최적의 해를 구할 수는 없음
최적의 해에 가까운 값을 구하는 방법 중의 하나임
일단, 탐욕 알고리즘에 타당한 정렬인지 확인해야 될 필요가 있음
예시: 최소 선택
시작 -> (7, 10)
7 -> (12, 15)
10 -> (5, 7)
탐욕 알고리즘 방식: 시작 -> 7 -> 12 => 19
하지만, 실제 최소값을 가지는 경우: 시작 -> 10 -> 5 => 15
최단 경로 알고리즘의 이해
최단경로 문제 : 두 노드를 잇는 가장 짧은 경로를 찾는 문제
가중치 그래프 (Weighted Graph)에서 간선(Edge)의 가중치 합이 최소가 되도록 하는 경로를 찾는 것이 목적
최단 경로 문제 종류
1. 단일 출발 및 단일 도착(single-source and single-destination) 최단 경로 문제
그래프 내의 특정 노드 u에서 출발, 또 다른 특정 노드 v에 도착하는 가장 짧은 경로를 찾는 문제
2. 단일 출발(single-destination) 최단 경로 문제
그래프 내의 특정 노드 u와 그래프 내 다른 모든 노드 각각의 가장 짧은 경로를 찾는 문제
A -> B, C, D(모든 노드)로 가는 각각의 가장 짧은 경로 문제
3. 전체 쌍(all-pair) 최단 경로
그래프 내의 모든 노드 쌍(u, v)에 대한 최단 경로를 찾는 문제
최단 경로 알고리즘 : Dijkstra Algorithm (다익스트라 알고리즘)
단일 출발 최단 경로 문제에 해당함
하나의 노드에서 다른 모든 노드 간의 각각 가장 짧은 거리를 구하는 문제
다익스트라(Dijkstra) 알고리즘 로직
첫 노드를 기준으로 연결되어 있는 정점들을 추가해 가며, 최단 거리를 갱신하는 기법
최단거리의 경우, 직접적으로 노드로 가는 거리 보다 다른 노드를 통해서 가는 경우가 더 짧을 수도 있음
예시) A-C > A-B-C
그래서, 모든 간선(노드 간의 거리)들을 합치면서 경로 길이를 비교해 볼 필요가 있음
즉, 첫 노드를 기준으로 다른 노드들을 순회하면서, 각 노드들 간의 거리를 고려하여 최단 거리만 반영하여 최종적으로 첫 노드에서 모든 노드 까지의 최단 거리를 구할 수 있음
순회하는 과정이 너비우선탐색(BFS)과 유사
첫 정점부터 각 노드 간의 거리를 저장하는 배열을 만듦
첫 정점의 인접 노드 간의 거리부터 먼저 계산
첫 정점부터 해당 노드간의 가장 짧은 거리를 해당 배열에 업데이트
필요한 자료
배열 : 결과적으로 첫 노드(기준 노드)에서 다른 모든 노드 까지의 최단 거리를 저장하기 위한 공간
예시) A - (B, C, D, E, F)의 최단 거리들을 저장
우선순위 큐 : 인접 노드를 순회하며 거리가 저장된 배열과 비교하기 위한 최단 거리 기준 순서에 활용됨
우선순위 큐를 활용하게 되면, 최단거리만 계속 빠르게 배열에 반영되고
거리가 길어 쌓인 노드들의 경우에는 나중에 비교 되어 계산이 줄어 듦
우선순위가 아니라면 긴 거리의 값도 배열에 참여하게 되어 Update하는 횟수가 늘어나게 됨