Knapsack in Cobol: Complete Solution & Deep Dive Guide

two sets of brown Code Happy marquee lights

Mastering Dynamic Programming: The Complete Guide to the Knapsack Problem in Cobol

The 0/1 Knapsack problem is a classic algorithmic puzzle that serves as a cornerstone for understanding dynamic programming. It challenges you to maximize value within a fixed capacity, a scenario surprisingly common in logistics, finance, and resource management, all domains where robust languages like Cobol have historically excelled.


You've stared at the problem description, a list of items with weights and values, and a knapsack with a limited capacity. The goal seems simple: pack the most valuable combination of items without breaking the bag. But as you start to think through the possibilities, the complexity explodes. A brute-force approach, checking every single combination, would take an eternity for even a moderate number of items. You feel stuck, knowing there must be a smarter, more systematic way to find the optimal solution.

This is a common hurdle for developers diving into combinatorial optimization. The leap from simple loops to dynamic programming can feel daunting. But what if you could unlock a powerful technique that breaks this massive problem down into manageable, bite-sized pieces? This guide will walk you through that very technique. We will demystify the 0/1 Knapsack problem and provide a complete, heavily-commented solution in Cobol, a language built for precision and reliability, proving that classic algorithms and legacy systems can create incredibly powerful solutions.


What is the 0/1 Knapsack Problem?

The 0/1 Knapsack problem is a fundamental challenge in combinatorial optimization. The name "0/1" signifies the core constraint: for each item, you have a binary choice. You either take the entire item (1) or you leave it behind (0). You cannot take a fraction of an item, which distinguishes it from the simpler "Fractional Knapsack" problem.

Let's formalize the components:

  • Items: A collection of objects, each with two attributes: a weight (w) and a value (v). Both are positive integers.
  • Knapsack Capacity (W): A maximum total weight that the knapsack can hold.
  • The Goal: To select a subset of items whose total weight does not exceed the capacity W, and whose total value is maximized.

Consider a simple example. If your knapsack capacity is 10 kg, and you have an item that weighs 11 kg, you simply cannot take it, no matter how valuable it is. If you have two items, one weighing 5 kg (value 10) and another weighing 6 kg (value 12), you can take either one, but not both, as their combined weight (11 kg) exceeds the 10 kg capacity. The optimal choice would be the second item for a value of 12.

The problem's difficulty stems from the interdependence of these choices. Taking one heavy, valuable item might prevent you from taking multiple lighter, less valuable items that, in combination, could yield a higher total value. This is why a simple "greedy" approach of picking the most valuable or densest items first often fails to produce the optimal result.


Why is Dynamic Programming the Key to the Solution?

Dynamic Programming (DP) is an algorithmic paradigm that solves complex problems by breaking them down into simpler, overlapping subproblems. Instead of re-calculating solutions to these subproblems repeatedly, it stores their results in a memory structure (like a table or an array) and reuses them as needed. This technique is perfectly suited for the Knapsack problem due to two key properties it exhibits:

  1. Optimal Substructure: The optimal solution to the main problem can be constructed from the optimal solutions of its subproblems. For Knapsack, the maximum value for i items and capacity W depends on the maximum value achievable with i-1 items at capacities W and W - weight[i].
  2. Overlapping Subproblems: A naive recursive solution would solve the same subproblems over and over again. For instance, the optimal value for a capacity of 5 kg might be needed when considering item 3, item 4, and item 5. DP calculates this once and stores it.

The standard DP approach involves building a 2D table, often denoted as dp[i][w], where:

  • i represents the number of items considered so far (from item 1 to item i).
  • w represents the current knapsack capacity being evaluated (from 0 to W).
  • The value stored at dp[i][w] is the maximum possible value that can be achieved using the first i items with a knapsack of capacity w.

The core logic for filling each cell in this table is a decision-making process for the i-th item:

  • Option 1: Don't take item i. In this case, the maximum value is simply the best we could do with the previous i-1 items at the same capacity w. This is dp[i-1][w].
  • Option 2: Take item i. This is only possible if the item's weight (weight[i]) is less than or equal to the current capacity w. If we take it, its value (value[i]) is added to the maximum value we could get with the remaining capacity (w - weight[i]) using the previous i-1 items. This is value[i] + dp[i-1][w - weight[i]].

The value of dp[i][w] is the maximum of these two options. By filling the table systematically, the final cell, dp[N][W] (where N is the total number of items), will contain the maximum possible value for the entire problem.

Visualizing the DP Table Logic

Here is a conceptual flow of how a single cell in the DP table is calculated, illustrating the decision process.

    ● Start: Calculate DP[i][w]
    │
    ▼
  ┌───────────────────────────┐
  │ Get value from row above: │
  │   Val_Without_Item = DP[i-1][w]   │
  └─────────────┬─────────────┘
                │
                ▼
  ◆ Is weight[i] <= w ?
  ╱                       ╲
 Yes                       No
  │                         │
  ▼                         │
┌─────────────────────────┐ │
│ Calculate value if item │ │
│ is included:            │ │
│ Val_With_Item =         │ │
│ value[i] +              │ │
│ DP[i-1][w - weight[i]]  │ │
└──────────┬──────────────┘ │
           │                │
           ▼                │
┌─────────────────────────┐ │
│ DP[i][w] = MAX(         │ │
│  Val_With_Item,         │ │
│  Val_Without_Item       │ │
│ )                       │ │
└──────────┬──────────────┘ │
           │                │
           └─────────┬──────┘
                     │
                     ▼
                 ┌───────────────────┐
                 │ DP[i][w] =        │
                 │   Val_Without_Item  │
                 └───────────────────┘
                     │
                     ▼
                 ● End: Cell Populated

How to Implement the Knapsack Solution in Cobol

Cobol, with its strong data structures and explicit procedural logic, is well-suited for implementing tabular algorithms like dynamic programming. Its fixed-format data definitions make creating the DP table straightforward. The verbose nature of the language also makes the logic, once written, exceptionally clear and easy to follow.

Below is a complete, well-commented Cobol program that solves the 0/1 Knapsack problem. We'll define a set of items and a maximum capacity, then use nested loops to populate our DP table and find the optimal value.

The Complete Cobol Source Code


       IDENTIFICATION DIVISION.
       PROGRAM-ID. KnapsackSolver.
       AUTHOR. Kodikra.
      *================================================================
      * This program solves the 0/1 Knapsack problem using dynamic
      * programming. It calculates the maximum value of items that
      * can be carried in a knapsack of a given capacity.
      * This solution is part of the exclusive kodikra.com curriculum.
      *================================================================
       DATA DIVISION.
       WORKING-STORAGE SECTION.
      *--- Problem Definition ---
       01 KNAPSACK-CAPACITY         PIC 9(04) VALUE 10.
       01 ITEM-COUNT                PIC 9(04) VALUE 4.

      *--- Item Data Table ---
      * Defines the list of available items with their weights and values.
       01 ITEM-TABLE.
          05 ITEM-RECORDS OCCURS 4 TIMES INDEXED BY ITEM-IDX.
             10 ITEM-WEIGHT        PIC 9(04).
             10 ITEM-VALUE         PIC 9(04).

      *--- Dynamic Programming Table ---
      * DP-TABLE(I, W) will store the maximum value achievable using the
      * first 'I' items with a capacity of 'W'.
      * Table size is (ITEM-COUNT + 1) x (KNAPSACK-CAPACITY + 1)
      * We use +1 to handle 0-based and 1-based logic easily.
       01 DP-TABLE-AREA.
          05 DP-ROW OCCURS 5 TIMES INDEXED BY I.
             10 DP-CELL OCCURS 11 TIMES INDEXED BY W
                PIC 9(05) VALUE 0.

      *--- Loop Counters and Temporary Variables ---
       01 I                         PIC 9(04).
       01 W                         PIC 9(04).
       01 CURRENT-ITEM-WEIGHT       PIC 9(04).
       01 CURRENT-ITEM-VALUE        PIC 9(04).
       01 VALUE-WITHOUT-ITEM        PIC 9(05).
       01 VALUE-WITH-ITEM           PIC 9(05).
       01 REMAINING-CAPACITY        PIC 9(04).
       01 FINAL-MAX-VALUE           PIC 9(05).

       PROCEDURE DIVISION.
       MAIN-LOGIC.
           PERFORM INITIALIZE-ITEM-DATA.
           PERFORM SOLVE-KNAPSACK.
           PERFORM DISPLAY-RESULT.
           STOP RUN.

      *----------------------------------------------------------------
      * Initializes the item data. In a real application, this
      * data would likely be read from a file or database.
      *----------------------------------------------------------------
       INITIALIZE-ITEM-DATA.
           MOVE 5 TO ITEM-WEIGHT(1).
           MOVE 10 TO ITEM-VALUE(1).

           MOVE 4 TO ITEM-WEIGHT(2).
           MOVE 40 TO ITEM-VALUE(2).

           MOVE 6 TO ITEM-WEIGHT(3).
           MOVE 30 TO ITEM-VALUE(3).

           MOVE 3 TO ITEM-WEIGHT(4).
           MOVE 50 TO ITEM-VALUE(4).

      *----------------------------------------------------------------
      * The core dynamic programming logic to solve the problem.
      * It iterates through each item and each possible capacity.
      *----------------------------------------------------------------
       SOLVE-KNAPSACK.
      *    Outer loop: Iterate through each item from 1 to ITEM-COUNT
           PERFORM VARYING I FROM 2 BY 1 UNTIL I > ITEM-COUNT + 1
      *        Inner loop: Iterate through each capacity from 1 to KNAPSACK-CAPACITY
               PERFORM VARYING W FROM 2 BY 1 UNTIL W > KNAPSACK-CAPACITY + 1
      *            Set the index for the DP table and item table
                   SET I TO I.
                   SET W TO W.
                   SET ITEM-IDX TO I - 1.

      *            Get the current item's properties
                   MOVE ITEM-WEIGHT(ITEM-IDX) TO CURRENT-ITEM-WEIGHT.
                   MOVE ITEM-VALUE(ITEM-IDX) TO CURRENT-ITEM-VALUE.

      *            Case 1: Don't include the current item.
      *            The value is the same as the max value from the previous item.
                   SET I DOWN BY 1.
                   MOVE DP-CELL(I, W) TO VALUE-WITHOUT-ITEM.
                   SET I UP BY 1.

      *            Check if the current item can fit in the current capacity
                   IF CURRENT-ITEM-WEIGHT <= W - 1 THEN
      *                Case 2: Include the current item.
      *                Value = current item's value + max value for remaining capacity.
                       COMPUTE REMAINING-CAPACITY = W - CURRENT-ITEM-WEIGHT.
                       SET I DOWN BY 1.
                       MOVE DP-CELL(I, REMAINING-CAPACITY) TO VALUE-WITH-ITEM.
                       SET I UP BY 1.
                       ADD CURRENT-ITEM-VALUE TO VALUE-WITH-ITEM
                   ELSE
      *                Item doesn't fit, so this option is not possible.
                       MOVE 0 TO VALUE-WITH-ITEM
                   END-IF

      *            Store the maximum of the two cases in the DP table.
                   IF VALUE-WITH-ITEM > VALUE-WITHOUT-ITEM THEN
                       MOVE VALUE-WITH-ITEM TO DP-CELL(I, W)
                   ELSE
                       MOVE VALUE-WITHOUT-ITEM TO DP-CELL(I, W)
                   END-IF
               END-PERFORM
           END-PERFORM.

      *----------------------------------------------------------------
      * Displays the final result found in the last cell of the DP table.
      *----------------------------------------------------------------
       DISPLAY-RESULT.
           SET I TO ITEM-COUNT + 1.
           SET W TO KNAPSACK-CAPACITY + 1.
           MOVE DP-CELL(I, W) TO FINAL-MAX-VALUE.
           DISPLAY "=============================================".
           DISPLAY "Kodikra Knapsack Problem Solver".
           DISPLAY "=============================================".
           DISPLAY "Knapsack Capacity: " KNAPSACK-CAPACITY.
           DISPLAY "Number of Items:   " ITEM-COUNT.
           DISPLAY "---------------------------------------------".
           DISPLAY "Maximum achievable value: " FINAL-MAX-VALUE.
           DISPLAY "=============================================".

       END PROGRAM KnapsackSolver.

Cobol Program Flow Diagram

This ASCII diagram illustrates the high-level execution flow of the PROCEDURE DIVISION in our Cobol program.

    ● START
    │
    ▼
  ┌─────────────────────────┐
  │ PERFORM                 │
  │ INITIALIZE-ITEM-DATA    │
  └───────────┬─────────────┘
              │
              ▼
  ┌─────────────────────────┐
  │ PERFORM SOLVE-KNAPSACK  │
  │                         │
  │   (Main DP Logic)       │
  └───────────┬─────────────┘
              │
              ├─> Loop I from 2 to N+1
              │   │
              │   └─> Loop W from 2 to Capacity+1
              │       │
              │       ├─> Get current item's properties
              │       │
              │       ├─> Calculate VALUE-WITHOUT-ITEM
              │       │
              │       ├─> Calculate VALUE-WITH-ITEM (if it fits)
              │       │
              │       └─> Set DP-CELL(I, W) = max(...)
              │
              ▼
  ┌─────────────────────────┐
  │ PERFORM DISPLAY-RESULT  │
  └───────────┬─────────────┘
              │
              ▼
    ● STOP RUN

Detailed Code Walkthrough

Let's break down the Cobol code into its key sections to understand how it works.

1. DATA DIVISION Setup

  • KNAPSACK-CAPACITY and ITEM-COUNT: These are constants that define the problem's constraints. We set the capacity to 10 and the number of items to 4.
  • ITEM-TABLE: This is an array (defined with OCCURS) that holds our items. Each entry has a ITEM-WEIGHT and ITEM-VALUE. The INDEXED BY ITEM-IDX clause creates an index for efficient access.
  • DP-TABLE-AREA: This is the heart of our solution. It's a 2D array defined as a table of rows (DP-ROW), where each row contains a table of cells (DP-CELL). We define its size as (ITEM-COUNT + 1) x (KNAPSACK-CAPACITY + 1). The "+1" is a common trick in DP to create a 0-th row and 0-th column, which simplifies the logic by representing the state of having "zero items" or "zero capacity." All cells are initialized to 0.
  • Temporary Variables: We declare variables like I and W for our loop counters, and others like VALUE-WITH-ITEM to hold intermediate calculations, making the code more readable.

2. PROCEDURE DIVISION Logic

  • MAIN-LOGIC: This is the program's entry point. It orchestrates the execution by calling three main paragraphs (subroutines) in order: initialize data, solve the problem, and display the output.
  • INITIALIZE-ITEM-DATA: Here, we hard-code the item data into our ITEM-TABLE. In a production environment, this data would typically be loaded from a file.
  • SOLVE-KNAPSACK: This is where the dynamic programming algorithm is implemented.
    • Nested PERFORM VARYING Loops: We use two nested loops to iterate through every cell of our DP-TABLE. The outer loop (I) iterates through the items, and the inner loop (W) iterates through the possible knapsack capacities. We start from 2 because the 1st row/column (index 1) represents the base cases (0 items/0 capacity) and is already initialized to zero.
    • Index Management: Cobol indices are 1-based. We use SET I TO I and SET W TO W to link our loop variables to the table indices. Note that our ITEM-TABLE is 0-indexed in concept (item 1 is at index 1), so we use SET ITEM-IDX TO I - 1 to access the correct item.
    • Calculating Values: Inside the loop, we fetch the VALUE-WITHOUT-ITEM from the cell directly above (DP-CELL(I-1, W)). Then, we check if the current item fits. If it does, we calculate VALUE-WITH-ITEM by adding the item's value to the stored optimal value for the remaining capacity.
    • The Decision: An IF statement compares VALUE-WITH-ITEM and VALUE-WITHOUT-ITEM. The greater of the two is stored in the current cell, DP-CELL(I, W), representing the new optimal value for that state.
  • DISPLAY-RESULT: After the loops complete, the answer to our problem is in the very last cell of the table: DP-CELL(ITEM-COUNT + 1, KNAPSACK-CAPACITY + 1). This paragraph retrieves that value and displays it in a formatted, user-friendly way.

When to Use This Approach (and When Not To)

The dynamic programming solution for the 0/1 Knapsack problem is powerful and guarantees an optimal solution. However, like any algorithm, it has specific characteristics that make it suitable for certain scenarios and less so for others.

Understanding its time and space complexity is crucial. The algorithm's performance is dictated by the size of the DP table we must build, which is N * W (number of items times knapsack capacity). Therefore, both the time complexity and space complexity are O(N * W).

This is known as a pseudo-polynomial time complexity. It's polynomial in the value of the capacity W, but exponential in the number of bits required to represent W. In practical terms, this means the solution is highly efficient if the capacity W is a reasonably small integer, but it can become very slow and memory-intensive if W is extremely large.

Pros and Cons of the Dynamic Programming Approach

Pros Cons
Guaranteed Optimality: Unlike greedy algorithms, the DP approach systematically explores all relevant subproblems to find the true maximum value. High Space Complexity: Requires O(N * W) memory to store the DP table, which can be prohibitive for very large capacities.
Conceptually Clear: The tabular method is systematic and relatively easy to reason about and debug once the core recurrence relation is understood. Pseudo-Polynomial Time: Performance degrades significantly as the integer value of the capacity W increases, making it impractical for problems with astronomical capacities.
Widely Applicable: The underlying principles can be adapted to solve many other optimization problems with similar structures. Not for Fractional Items: This specific implementation is strictly for the 0/1 variant. A different, simpler greedy algorithm is optimal for the Fractional Knapsack problem.
Efficient for Moderate Constraints: For typical competitive programming or academic scenarios where N and W are within reasonable bounds (e.g., N < 1000, W < 10000), this method is very effective. Integer Weights Only: The standard DP approach assumes integer weights, as they are used as indices into the DP table. Non-integer weights would require a different approach.

Real-World Applications of the Knapsack Problem

While it might seem like an abstract puzzle, the Knapsack problem models a vast array of real-world optimization challenges where a selection must be made to maximize "value" while adhering to a "budget" or "capacity."

  • Resource Allocation: In cloud computing, a server has a limited capacity (CPU, RAM). The Knapsack algorithm can help decide which set of virtual machines or containers (each with a resource "weight" and business "value") to run on that server to maximize utility.
  • Financial Portfolio Optimization: An investor has a fixed amount of capital (the "capacity") and a list of potential investments (the "items"). Each investment has a cost (the "weight") and an expected return (the "value"). The 0/1 Knapsack model can be used to select a portfolio of investments that maximizes expected return without exceeding the budget.
  • Cargo Loading: A shipping company has a cargo plane or truck with a maximum weight capacity. They have a list of items to transport, each with a weight and a shipping fee (value). The algorithm helps determine the most profitable combination of items to load onto the vehicle.
  • Cutting Stock Problem: In manufacturing, you need to cut standard-sized materials (e.g., rolls of paper, steel beams) into smaller pieces of specified lengths to fulfill orders. This can be framed as a Knapsack problem where the goal is to select pieces to cut from a stock roll to minimize waste (which is equivalent to maximizing the value of the cut pieces).
  • Project Selection: A manager has a limited budget and a list of potential projects. Each project requires a certain investment and is expected to generate a certain amount of profit. The goal is to select the set of projects that will yield the maximum total profit.

Alternative Approaches and Considerations

While our dynamic programming solution is standard and robust, it's beneficial to be aware of other ways to approach the problem.

1. Recursion with Memoization (Top-Down DP)

This is an alternative way to implement dynamic programming. Instead of building the table from the bottom up, you write a recursive function that solves the problem from the top down. To avoid re-computing the same subproblems, you store the results in a lookup table (a "memo"). When the function is called, it first checks if the result for the given state is already in the memo. If so, it returns it; otherwise, it computes the result, stores it, and then returns it.

This approach can sometimes be more intuitive to write if you think recursively, and it has the advantage of only computing the states that are actually reachable from the top, which might be a subset of the entire table.

2. Space-Optimized Dynamic Programming

A major drawback of our solution is the O(N * W) space complexity. It's possible to optimize this down to O(W). Notice that when calculating the values for the current row i, we only ever need information from the previous row i-1. This means we don't need to store the entire 2D table.

We can use a single 1D array of size W+1. To ensure we are using the values from the "previous" row's calculation, we must iterate the inner capacity loop in reverse order (from W down to the current item's weight). This is a common and powerful optimization for Knapsack-style problems.

3. Brute-Force (Exponential)

The most naive approach is to generate every possible subset of items. For N items, there are 2N possible subsets. For each subset, you would check if its total weight is within the capacity and, if so, calculate its total value. You would then keep track of the maximum value found.

This approach is extremely slow and becomes computationally infeasible for anything more than about 20-25 items. Its time complexity is O(2N), which highlights why dynamic programming is so essential for solving this problem efficiently.


Frequently Asked Questions (FAQ)

What's the difference between 0/1 Knapsack and Fractional Knapsack?

In the 0/1 Knapsack problem, you must make a binary choice for each item: either take the whole item or leave it entirely. In the Fractional Knapsack problem, you are allowed to take fractions of items. This makes the Fractional problem much easier; it can be solved optimally with a simple greedy algorithm by taking items in descending order of their value-to-weight ratio until the knapsack is full.

Is Cobol a good language for solving algorithmic problems like Knapsack?

While not traditionally seen as an "algorithmic" language like Python or C++, Cobol is perfectly capable of implementing complex logic. Its strengths lie in its structured data definitions (OCCURS for arrays) and clear, procedural control flow (PERFORM loops). For algorithms that rely on table manipulation, like dynamic programming, Cobol's rigidity can actually make the code's intent very explicit and stable, which is valuable in enterprise systems.

Can the Knapsack solution handle items with zero weight or value?

Yes. The provided dynamic programming logic handles these cases correctly. An item with zero weight can always be taken without consuming capacity, so it will be included if its value is positive. An item with zero value will never be chosen over an option with positive value, which is the correct optimal behavior.

How can I optimize the space complexity of the dynamic programming solution?

The space complexity can be reduced from O(N * W) to O(W) by using a 1D array instead of a 2D table. Since the calculation for the current row i only depends on the previous row i-1, you only need one array to store the results. The key is to iterate the inner capacity loop backwards (from W down to 1) to ensure you are using the results from the previous item's iteration, not the current one.

What does "NP-hard" mean in the context of the Knapsack problem?

NP-hard (Non-deterministic Polynomial-time hard) is a complexity class for problems that are at least as hard as the hardest problems in NP. In simple terms, it means there is no known algorithm that can solve the problem in polynomial time in the worst case. Our DP solution is pseudo-polynomial, not polynomial, because its runtime depends on the magnitude of the capacity W, not just the size of the input (number of items).

How do I handle a very large number of items in Cobol?

If the number of items (ITEM-COUNT) becomes very large, the primary constraint in Cobol is memory for the DP-TABLE. You might need to ensure your system's memory allocation (REGION size in JCL on a mainframe) is sufficient. If both N and W are large, the standard DP approach may be too slow or memory-intensive, and you might need to explore approximation algorithms or other advanced techniques like branch and bound.


Conclusion: Timeless Problems, Robust Solutions

The 0/1 Knapsack problem is more than just an academic exercise; it's a blueprint for solving a wide range of optimization challenges that appear in countless industries. By mastering the dynamic programming approach, you gain a powerful tool for breaking down seemingly intractable problems into a logical, step-by-step process that guarantees an optimal solution.

Implementing this solution in Cobol demonstrates the enduring capability of a language designed for business-critical, data-intensive tasks. It proves that the principles of sound algorithmic design are universal and can be applied effectively in any environment, from modern cloud platforms to the mainframes that still power the global economy. The structure and discipline required by Cobol force a clear implementation, resulting in code that is not only correct but also maintainable and robust.

Ready to tackle more challenges and deepen your understanding of algorithms and enterprise programming? Explore the complete Cobol learning path on kodikra.com and continue your journey from foundational concepts to advanced problem-solving. For a broader view of our offerings, check out our full catalog of language guides and modules.

Disclaimer: The code and concepts in this article are based on standard GnuCOBOL or IBM Enterprise COBOL compilers. Syntax and features may vary slightly between different Cobol versions. Always consult your specific compiler's documentation for compatibility.


Published by Kodikra — Your trusted Cobol learning resource.