RosettaCodeData/Task/Knapsack-problem-Bounded/Fortran/knapsack-problem-bounded.f

185 lines
7.3 KiB
Fortran

module knapsack_mod
implicit none
!--------------------------------------------------------------------
! Define an item type with a name, weight, value, and available count.
!--------------------------------------------------------------------
type :: item
character(len=24) :: name ! Name (for display purposes)
integer :: weight ! Weight of one copy of the item
integer :: value ! Value of one copy of the item
integer :: count ! Maximum number of copies available
end type item
!--------------------------------------------------------------------
! Define a parameter array of items.
!--------------------------------------------------------------------
type(item), parameter :: items(*) = [ &
item("map ", 9, 150, 1), &
item("compass ", 13, 35, 1), &
item("water ", 153, 200, 2), &
item("sandwich ", 50, 60, 2), &
item("glucose ", 15, 60, 2), &
item("tin ", 68, 45, 3), &
item("banana ", 27, 60, 3), &
item("apple ", 39, 40, 3), &
item("cheese ", 23, 30, 1), &
item("beer ", 52, 10, 3), &
item("suntan cream ", 11, 70, 1), &
item("camera ", 32, 30, 1), &
item("T-shirt ", 24, 15, 2), &
item("trousers ", 48, 10, 2), &
item("umbrella ", 73, 40, 1), &
item("waterproof trousers ", 42, 70, 1), &
item("waterproof overclothes ", 43, 75, 1), &
item("note-case ", 22, 80, 1), &
item("sunglasses ", 7, 20, 1), &
item("towel ", 18, 12, 2), &
item("socks ", 4, 50, 1), &
item("book ", 30, 10, 2) &
]
contains
!--------------------------------------------------------------------
! Function: knapsack
!
! Description:
! Solves the bounded knapsack problem using dynamic programming.
! This version retains a two-dimensional DP table (m) and applies
! binary splitting to efficiently handle items available in multiple
! copies.
!
! Input:
! w - Maximum weight capacity of the knapsack.
!
! Output:
! s - An integer array (of size equal to the number of items)
! where s(i) indicates how many copies of item i are selected
! in the optimal solution.
!--------------------------------------------------------------------
function knapsack(w) result(s)
integer, intent(in) :: w ! Knapsack capacity
integer, allocatable :: s(:) ! Solution vector of item counts
integer, allocatable :: m(:,:) ! DP table: m(i,j) is the maximum value using items 1..i with capacity j
integer :: n ! Total number of items
integer :: i, j, v, k ! Loop indices and temporary value
! Variables for binary splitting of the count of an item.
integer :: available ! Remaining copies to process for the current item
integer :: r ! Current binary splitting factor
integer :: k_group ! Number of copies in the current group
integer :: group_weight ! Total weight of the current group (k_group * item weight)
integer :: group_value ! Total value of the current group (k_group * item value)
! Determine the number of items available.
n = size(items)
! Allocate the solution vector and DP table.
! DP table m is sized from row 0 (base case: no items) to n, and column 0 to w.
allocate(s(n), m(0:n, 0:w))
! Initialize both the DP table and the solution vector to 0.
m = 0
s = 0
!-------------------------------
! DP Table Construction
!-------------------------------
! For each item i (from 1 to n), determine the best value achievable
! with a knapsack capacity from 0 to w.
do i = 1, n
! First, copy the previous row into the current row.
! This means if we do not take any of item i, the value remains as before.
do j = 0, w
m(i, j) = m(i-1, j)
end do
! Process item i using binary splitting:
! Instead of iterating k from 1 to items(i)%count one by one, we split
! the available copies into groups for an efficient "0/1 item" update.
available = items(i)%count
r = 1
do while (available > 0)
! Use k_group copies, which is the minimum of the current binary factor and available copies.
k_group = min(r, available)
! Compute group weight and value for k_group copies.
group_weight = k_group * items(i)%weight
group_value = k_group * items(i)%value
! Perform a 0/1 knapsack update for this group.
! Loop backwards from capacity w down to group_weight so that each group
! is only used once. We update row i (which already contains m(i-1, :) as baseline).
do j = w, group_weight, -1
! If adding this group improves the total value, update m(i,j).
v = m(i, j - group_weight) + group_value
if (v > m(i, j)) then
m(i, j) = v
end if
end do
! Subtract the number of copies processed and double the binary factor.
available = available - k_group
r = r * 2
end do
end do
!-------------------------------
! Backtracking to Retrieve the Solution
!-------------------------------
! Starting from the maximum capacity and the last item, deduce how many copies
! of each item were used in the optimal solution.
j = w
do i = n, 1, -1
! Store the optimal value for items 1..i with current capacity j.
v = m(i, j)
! For item i, try every possible count from 0 to items(i)%count.
do k = 0, items(i)%count
if (j >= k * items(i)%weight) then
! Check if the current value resulted from taking k copies of item i.
if (v == m(i-1, j - k*items(i)%weight) + k*items(i)%value) then
s(i) = k ! Record k copies for item i
j = j - k*items(i)%weight ! Decrease the remaining capacity
exit ! Proceed to the next (previous) item
end if
end if
end do
end do
end function knapsack
end module knapsack_mod
program main
use knapsack_mod
implicit none
integer, allocatable :: s(:)
integer :: i, total_count, total_weight, total_value
s = knapsack(400)
total_count = 0
total_weight = 0
total_value = 0
write(*,'(A22 A6 A7 A6)') 'Item', 'Count', 'Weight', 'Value'
write(*,'("------------------------------------------------")')
do i = 1, size(items)
if (s(i) > 0) then
write(*,'(A22 I5 I6 I6)') &
items(i)%name, s(i), s(i)*items(i)%weight, s(i)*items(i)%value
total_count = total_count + s(i)
total_weight = total_weight + s(i)*items(i)%weight
total_value = total_value + s(i)*items(i)%value
end if
end do
write(*,'("------------------------------------------------")')
write(*,'(A22 I5 I6 I6)') 'Totals:', total_count, total_weight, total_value
end program main