$G = (V, E)$
$Y = \{ v_1 \}$
$F = \emptyset$
while (사례 미해결):
- $v_1$에서 출발하여 $Y$에 속한 마디만 중간경로로 사용해서 갈 수 있는 마디 중에서
$v_1$으로부터 가장 짧은 경로를 갖는 마디 $v \in (V-Y)$ 선택.
- 선택된 마디를 $Y$에 추가.
- 해당 마디 선택에 사용된 이음선을 $F$에 추가.
if ($Y == V$):
사례해결
from math import inf
from collections import defaultdict
def dijkstra(W):
V = len(W)
F = defaultdict(list) # 최단경로를 구성하는 이음선들의 집합
touch = [0] * V
length = [W[0][i] for i in range(V)]
length[0] = -1
for _ in range(V-1):
min = inf
for i in range(V):
if (0 < length[i] < min):
min = length[i]
vnear = i
F[touch[vnear]].append(vnear)
for i in range(V):
if (length[vnear] + W[vnear][i] < length[i]):
length[i] = length[vnear] + W[vnear][i]
touch[i] = vnear
length[vnear] = -1
return F
W = [[0, 7, 4, 6, 1],
[inf, 0, inf, inf, inf],
[inf, 2, 0, 5, inf],
[inf, 3, inf, 0, inf],
[inf, inf, inf, 1, 0]]
dijkstra(W)
defaultdict(list, {0: [4, 2], 4: [3], 3: [1]})
length[vnear] = -1
명령문을 전체 반복문 맨 뒤로 옮겨야 함.dijstra()
함수에 출발점을 추가하면 됨.def dijkstra_gen(k, W):
V = len(W)
assert (0<= k < V)
F = defaultdict(list) # 최단경로를 구성하는 이음선들의 집합
touch = [k] * V
length = [W[k][i] for i in range(V)]
length[k] = -1 # v_k를 출발 마디로 지정
for _ in range(V-1):
min = inf
for i in range(V):
if (0 < length[i] < min):
min = length[i]
vnear = i
if min == inf:
return "일부 경로가 없어요."
F[touch[vnear]].append(vnear)
for i in range(V):
if (length[vnear] + W[vnear][i] < length[i]):
length[i] = length[vnear] + W[vnear][i]
touch[i] = vnear
length[vnear] = -1
return F
dijkstra_gen(0, W)
defaultdict(list, {0: [4, 2], 4: [3], 3: [1]})
dijkstra_gen(2, W)
'일부 경로가 없어요.'
dijkstra_gen(4, W)
'일부 경로가 없어요.'
물건1: 50만원, 5kg
물건2: 60만원, 10kg
물건3: 140만원, 20kg
W = 30일 경우 최적의 해
140 + 60 = 200(만원)
위 예제에서는 최적의 해를 제공하지만, 경우에 따라 달라짐.
반례: 물건4가 아래와 같이 추가되는 경우 가장 값비싼 문건을 먼저 선택하는 전략은 최적의 해를 제공하지 않음. (이유는?)
물건1: 50만원, 5kg
물건2: 60만원, 10kg
물건3: 140만원, 20kg
물건4: 30만원, 2kg
무게당 값어치가 가장 큰 물건 선택
물건1 1kg당 값어치: 10만원
물건2 1kg당 값어치: 6만원
물건3 1kg당 값어치: 7만원
따라서 아래 물건 선택
50만원: 5kg
140만원: 20kg
------------
190만원: 25kg
최적의 해 아님.
하지만 물건4가 추가되면 최적의 해를 제공함.
물건1 1kg당 값어치: 10만원
물건2 1kg당 값어치: 6만원
물건3 1kg당 값어치: 7만원
물건4 1kg당 값어치: 15만원
따라서 아래 물건 선택
30만원: 2kg
50만원: 5kg
140만원: 20kg
------------
220만원: 27kg
최적의 해 아님.
(n+1,W+1)
모양의 2차원 행렬 $P$ 생성from typing import NamedTuple
class Item(NamedTuple):
name: str
weight: int
value: float
<그림 출처:배낭 문제: 위키피디아>
items = [Item("item1", 1, 1),
Item("item2", 1, 2),
Item("item3", 2, 2),
Item("item4", 4, 10),
Item("item5", 12, 4)]
# 아이템(물건) 개수와 용량 한도
n = len(items)
W = 15
P
를 영행렬로 초기화 하기# (n+1,W+1) 모양
P = [[0.0 for _ in range(W+1)] for _ in range(n+1)]
for i, item in enumerate(items): # 행 인덱스(물건 번호)는 0부터 시작함에 주의
wi = item.weight # (i+1) 번째 아이템 무게
pi = item.value # (i+1) 번째 아이템 가치
for w in range(1, W + 1): # 열 인덱스(용량 한도) 역시 0부터 시작
previous_items_value = P[i][w] # i번 행값을 이미 계산하였음. 예를 들어, P[0][w] = 0.
if w >= wi: # 현재 아이템의 가방에 들어갈 수 있는 경우
previous_items_value_without_wi = P[i][w - wi]
P[i+1][w] = max(previous_items_value,
previous_items_value_without_wi + pi)
else: # 현재 아이템이 너무 무거운 경우
P[i+1][w] = previous_items_value
def knapsack(items, W):
"""
items: 아이템(물건)들의 리스트
W: 최대 저장용량
"""
# 아이템(물건) 개수
n = len(items)
# P[i][w]를 담는 2차원 행렬을 영행렬로 초기화
# (n+1) x (W+1) 모양
P = [[0.0 for _ in range(W+1)] for _ in range(n+1)]
for i, item in enumerate(items):
wi = item.weight # (i+1) 번째 아이템 무게
pi = item.value # (i+1) 번째 아이템 가치
for w in range(1, W + 1):
previous_items_value = P[i][w] # i번 행값을 이미 계산하였음. i는 0부터 시작함의 주의할 것
if w >= wi: # 현재 아이템의 무게가 가방에 들어갈 수 있는 경우
previous_items_value_without_wi = P[i][w - wi]
P[i+1][w] = max(previous_items_value,
previous_items_value_without_wi + pi)
else:
P[i+1][w] = previous_items_value
return P
def solution(items, W):
P = knapsack(items, W)
n = len(items)
w = W
# 선택 아이템 저장
selected = []
# 선택된 아이템을 역순으로 확인
for i in range(n, 0, -1):
if P[i - 1][w] != P[i][w]: # (i-1) 번째 아이템이 사용된 경우. 인덱스가 0부터 출발함에 주의
selected.append(items[i - 1])
w -= items[i - 1].weight # (i-1) 번째 아이템의 무게 제거
return selected
def max_value(items, W):
selected = solution(items, W)
sum = 0
for item in selected:
sum += item.value
return sum
for item in solution(items, 15):
print(item)
Item(name='item4', weight=4, value=10) Item(name='item3', weight=2, value=2) Item(name='item2', weight=1, value=2) Item(name='item1', weight=1, value=1)
max_value(items, 15)
15
items2 = [Item("item1", 1, 5),
Item("item2", 2, 10),
Item("item3", 1, 15)]
(4, 4)
모양의 행렬 $P$knapsack(items2, 3)
[[0.0, 0.0, 0.0, 0.0], [0.0, 5.0, 5.0, 5.0], [0.0, 5.0, 10.0, 15.0], [0.0, 15.0, 20.0, 25.0]]
for item in solution(items2, 3):
print(item)
Item(name='item3', weight=1, value=15) Item(name='item2', weight=2, value=10)
class Item1:
def __init__(self, name, weight, value):
self.name = name
self.weight = weight
self.value = value
items3 = [Item1("item1", 1, 1),
Item1("item2", 1, 2),
Item1("item3", 2, 2),
Item1("item4", 4, 10),
Item1("item5", 12, 4)]
for item in solution(items3, 15):
print(item)
<__main__.Item1 object at 0x7fc96e03a8b0> <__main__.Item1 object at 0x7fc96e03abb0> <__main__.Item1 object at 0x7fc96e03a820> <__main__.Item1 object at 0x7fc96e03a9a0>
__str__()
메서드 구현 필요class Item1:
def __init__(self, name, weight, value):
self.name = name
self.weight = weight
self.value = value
def __str__(self):
return 'Item(' + self.name + ', ' + str(self.weight) + ', ' + str(self.value) + ')'
items4 = [Item1("item1", 1, 1),
Item1("item2", 1, 2),
Item1("item3", 2, 2),
Item1("item4", 4, 10),
Item1("item5", 12, 4)]
for item in solution(items4, 15):
print(item)
Item(item4, 4, 10) Item(item3, 2, 2) Item(item2, 1, 2) Item(item1, 1, 1)
dijkstra()
함수가 항상 최단경로에 대한 정보를 생성함을 증명하라.
dijkstra()
함수는 최단경로에 포함된 이음선만 찾는다.
최단경로와 최단길이를 반환하는 함수 dijkstra_path()
함수를 구현하라.
아래 표로 표현되는 방향그래프의 마디 v4에서 다른 마디로 가는 최단경로를 구하는 과정을 단계별로 설명하라.
W = [[ 0, inf, 72, 50, 90, 35],
[inf, 0, 71, 70, 73, 75],
[ 72, 71, 0, inf, 77, 90],
[ 50, 70, inf, 0, 60, 40],
[ 90, 73, 77, 60, 0, 80],
[ 35, 75, 90, 40, 80, 0]]
다익스트라 알고리즘에 의해 v5에서 각 마디로 가는 최단경로를 찾는 과정은 다음과 같음.
다익스트라 알고리즘(이어짐)
dijkstra_gen()
함수를 이용한 아래 결과와 동일함.dijkstra_gen(4,W)
defaultdict(list, {4: [3, 1, 2, 5, 0]})