aboutsummaryrefslogtreecommitdiff
path: root/lib/knapsack_solver/solving_methods/branch_and_bound.rb
blob: 64d70923f469cefbdfa76f712d470701dfe96bca (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
module KnapsackSolver
  # This class implements methods for solving 0/1 knapsack problem using
  # Branch and Bound method.
  class BranchAndBound
    # Initializes instance of Brand and Bound 0/1 knapsack problem solver.
    #
    # @param instance [Instance] 0/1 knapsack problem instance
    def initialize(instance)
      @instance = instance
      @config = Array.new(instance.things.size)
    end

    # Solve the instance of 0/1 knapsack problem.
    #
    # @return [Hash] resulting price and thing configuration (0 = thing is not in the knapsack, 1 = thing is there)
    def run
      solve(0)
      { price: @best_price, config: @best_config }
    end

    protected

    # Solve the problem starting at specified thing.
    #
    # @param index [Integer] index of thing which will be decided (put in or out from the knapsack) the next
    def solve(index)
      @config[index] = 0
      solve(index + 1) unless stop(index)
      @config[index] = 1
      solve(index + 1) unless stop(index)
    end

    # Determine if solving of current branch should continue.
    #
    # @param index [Integer] index of the last decided thing so far
    # @return [true, false] weather to continue with solving current branch.
    def stop(index)
      # Update of the best price so far
      weight = config_weight(0, index)
      price = config_price(0, index)
      update_best_price(price, weight, index)
      # No more things to put into the knapsack
      return true if index >= (@instance.things.size - 1)
      # The knapsack is overloaded, do not continue this branch
      return true if weight > @instance.weight_capacity
      if instance_variable_defined?('@best_price') &&
         ((price + get_price_of_remaining_things(index + 1)) <= @best_price)
        # Adding all the ramining things does not produce better price
        return true
      end
      false
    end

    # Update the best price achieved so far.
    #
    # @param price [Integer] price of the current configuration
    # @param weight [Integer] weight of the current configuration
    # @param index [Integer] index of the next thing presence of which will be decided
    def update_best_price(price, weight, index)
      if !instance_variable_defined?('@best_price') ||
         ((weight <= @instance.weight_capacity) && (price > @best_price))
        @best_price = price
        valid_len = index + 1
        remaining = @config.size - index - 1
        # All undecided things will not be put into the knapsack
        @best_config = @config.slice(0, valid_len).fill(0, valid_len, remaining)
        @best_config_index = index
      end
    end

    # Gets weight of set of things. The set is subset of the things ordered by
    # their index.
    #
    # @param start_index [Integer] index of the first thing included in the set
    # @param end_index [Integer] index of the last thing included in the set
    # @return [Integer] weight of the things
    def config_weight(start_index, end_index)
      weight = 0
      @config[start_index..end_index].each_with_index do |presence, index|
        weight += presence * @instance.things[index].weight
      end
      weight
    end

    # Gets price of set of things. The set is subset of the things ordered by
    # their index.
    #
    # @param start_index [Integer] index of the first thing included in the set
    # @param end_index [Integer] index of the last thing included in the set
    # @return [Integer] price of the things
    def config_price(start_index, end_index)
      price = 0
      @config[start_index..end_index].each_with_index do |presence, index|
        price += presence * @instance.things[index].price
      end
      price
    end

    # Gets sum of prices of things for which their presence in the knapsack
    # was not decided yet.
    #
    # @param from_index [Integer] index of the first undecided thing
    # @return [Integer] price of the remaining things
    def get_price_of_remaining_things(from_index)
      price = 0
      to_index = @instance.things.size - 1
      @instance.things[from_index..to_index].each { |t| price += t.price }
      price
    end
  end
end