카테고리 없음

[Python] 냅색(KnapSack) 알고리즘

왕초보코딩러 2024. 10. 31. 15:01
728x90

배낭 문제라고도 불리는 냅색 알고리즘은 DP 문제 중 일부이다.

주어진 물건들 중 일부를 선택하여 제한된 용량의 가방(또는 배낭)에 최대한의 가치를 담는 문제를 해결하는 알고리즘

 

물건을 넣을 것인가, 안넣을 것인가를 정하는 것이 문제의 핵심이다.

 


평범한 배낭 문제를 이용하여 원리를 알아보겠다

https://www.acmicpc.net/problem/12865

 


일단 예제 1을 보면

4 7 # 물품 수 n 과 최대 무게 k 

# 각 물품의 무게와 가치
6 13 
4 8
3 6
5 12

 

 

물품 수 n+1 (아무 물품도 넣지 않았을 때)를 으로

무게 k+1 (무게가 0일 때)를 로 한

2차원 표를 만든다

2차원 표 안에는 가치가 들어간다.

각 행의 물건을 넣었을 때, 넣지 않았을 때의 가치를 비교해서 더 큰 값을 넣어준다

 

안넣는 경우

- 물건의 무게보다 수용할 수 있는 무게가 작을 때

- 안 넣었을 때 가치가 더 큰 경우


일단 물품이 없을 때, 무게가 0일 때는 모두 가치가 없기 때문에 0으로 초기화해준다.

 

이제 무게 6, 가치 13 짜리 물건을 넣을 지 말지, 고민해본다.

 

 

 

1 만큼의 무게만 수용할 수 있기 때문에, 물건을 넣지 못하고, 위의 행의 값을 가져온다

 

 

 

그러면 6을 수용할 수 있는 가방이 나올 때까지는 물건을 넣을 수 없다

그렇다면 6을 수용할 수 있을 때를 살펴본다

 

 

6만큼을 수용하는 가방에

6짜리 물건을

안 넣을 때는 0의 가치,

넣을 때는 13의 가치를 얻을 수 있다

 

 

 

7만큼을 수용하는 가방에

6짜리 물건을

안 넣을 때는 0의 가치가 나온다.

넣을 때는 6짜리 물건을 넣고도, 1만큼을 더 수용할 수 있기 때문에 13 + 물품X의 1짜리만큼 넣을 수 있다

 

 

이렇게 쭉 반복한다.

이번에는 

7만큼을 수용하는 가방에

3짜리 물건을 넣을지 말지 알아보겠다.

 

 

안 넣을 때는 13의 가치가 나온다.

넣을 때는 3짜리 물건을 넣고도, 4만큼을 더 수용할 수 있기 때문에 무게 4인 물건을 더 넣을 수 있다

그렇게 해서 최대로 14의 값을 넣을 수 있다

 

 

 

표를 완성시키면,

 


이제 지금까지의 것을 점화식으로 만들어보겠다

dp를 2차원 리스트로 정의하면

dp = [[0] * (k+1) for _ in range(n+1)]

 

# nn번 째 물건, kk 무게까지 수용하는 가방
# value[nn]은 nn의 가치
# weight[nn]은 nn의 무게
dp[nn][kk] = max(dp[nn - 1][kk], value[nn] + dp[nn - 1][kk - weight[nn]]) # 안 넣었을 때, 넣었을 때

 

 

평범한 배낭 문제를 푼다면

import sys
input = sys.stdin.readline

n, k = map(int, input().split())
wv = [[]]
for _ in range(n):
    w, v= map(int, input().split())
    wv.append((w,v))
    
# 냅색 점화식
# ns(n, w) = max(ns(n-1, w), ns(n-1, w-wv[i][0])+wv[i][1])

# 행: 들어가는 가방 수
# 열: 무게 수
dp = [[0]*(k+1) for _ in range(n+1)]

for i in range(1, n+1):
    w, v = wv[i]
    for j in range(1, k+1):
        if w > j:
            dp[i][j] = dp[i-1][j]
        else:
            dp[i][j] = max(dp[i-1][j], dp[i-1][j-w] +v) # 지금 가방 안들었을 때, 지금 가방 들었을 때
print(dp[n][k])

가 된다.