Towards a Memory-Efficient Knapsack DP Algorithm

Similar documents
Week - 04 Lecture - 01 Merge Sort. (Refer Slide Time: 00:02)

Framework for Design of Dynamic Programming Algorithms

CMSC Introduction to Algorithms Spring 2012 Lecture 16

(Refer Slide Time: 01.26)

Dynamic Programming Homework Problems

We augment RBTs to support operations on dynamic sets of intervals A closed interval is an ordered pair of real

Outline. Column Generation: Cutting Stock A very applied method. Introduction to Column Generation. Given an LP problem

Column Generation: Cutting Stock

Polynomial-Time Approximation Algorithms

Subset sum problem and dynamic programming

COMP 352 FALL Tutorial 10

Backtracking. Chapter 5

Lecture 8: Mergesort / Quicksort Steven Skiena

IN101: Algorithmic techniques Vladimir-Alexandru Paun ENSTA ParisTech

5.1 The String reconstruction problem

Theorem 2.9: nearest addition algorithm

Principles of Algorithm Design

Dynamic Programming Homework Problems

1 More on the Bellman-Ford Algorithm

Design and Analysis of Algorithms Prof. Madhavan Mukund Chennai Mathematical Institute

3 INTEGER LINEAR PROGRAMMING

Math 340 Fall 2014, Victor Matveev. Binary system, round-off errors, loss of significance, and double precision accuracy.

Algorithms for Data Science

CS Data Structures and Algorithm Analysis

PRAM Divide and Conquer Algorithms

Lecture Notes on Binary Decision Diagrams

Dynamic programming. Trivial problems are solved first More complex solutions are composed from the simpler solutions already computed

Conflict Graphs for Combinatorial Optimization Problems

B553 Lecture 12: Global Optimization

6.001 Notes: Section 4.1

Sankalchand Patel College of Engineering - Visnagar Department of Computer Engineering and Information Technology. Assignment

Memoization/Dynamic Programming. The String reconstruction problem. CS124 Lecture 11 Spring 2018

EECS 2011M: Fundamentals of Data Structures

Solutions to Problem 1 of Homework 3 (10 (+6) Points)

Algorithms. Ch.15 Dynamic Programming

COLUMN GENERATION IN LINEAR PROGRAMMING

Scan and Quicksort. 1 Scan. 1.1 Contraction CSE341T 09/20/2017. Lecture 7

1 Dynamic Programming

Divide-and-Conquer. Divide-and conquer is a general algorithm design paradigm:

Ensures that no such path is more than twice as long as any other, so that the tree is approximately balanced

The divide and conquer strategy has three basic parts. For a given problem of size n,

Greedy Algorithms 1 {K(S) K(S) C} For large values of d, brute force search is not feasible because there are 2 d {1,..., d}.

CMSC 451: Lecture 10 Dynamic Programming: Weighted Interval Scheduling Tuesday, Oct 3, 2017

Algorithm Analysis. Carlos Moreno uwaterloo.ca EIT Θ(n) Ω(log n) Θ(1)

Discrete Optimization. Lecture Notes 2

Applied Algorithm Design Lecture 3

Unit-2 Divide and conquer 2016

Exact Algorithms for NP-hard problems

1 Format. 2 Topics Covered. 2.1 Minimal Spanning Trees. 2.2 Union Find. 2.3 Greedy. CS 124 Quiz 2 Review 3/25/18

MergeSort, Recurrences, Asymptotic Analysis Scribe: Michael P. Kim Date: April 1, 2015

Hi everyone. Starting this week I'm going to make a couple tweaks to how section is run. The first thing is that I'm going to go over all the slides

COMP 161 Lecture Notes 16 Analyzing Search and Sort

Practice Problems for the Final

Divide and Conquer Algorithms

CS 350 Final Algorithms and Complexity. It is recommended that you read through the exam before you begin. Answer all questions in the space provided.

1 Maximum Independent Set

6. Lecture notes on matroid intersection

Algorithms and Data Structures (INF1) Lecture 7/15 Hua Lu

Scan and its Uses. 1 Scan. 1.1 Contraction CSE341T/CSE549T 09/17/2014. Lecture 8

LP-Modelling. dr.ir. C.A.J. Hurkens Technische Universiteit Eindhoven. January 30, 2008

Sorting: Overview/Questions

Constraint Satisfaction Problems

CS 506, Sect 002 Homework 5 Dr. David Nassimi Foundations of CS Due: Week 11, Mon. Apr. 7 Spring 2014

9.1 Cook-Levin Theorem

CSE 373: Data Structures and Algorithms

Elements of Dynamic Programming. COSC 3101A - Design and Analysis of Algorithms 8. Discovering Optimal Substructure. Optimal Substructure - Examples

Name: Lirong TAN 1. (15 pts) (a) Define what is a shortest s-t path in a weighted, connected graph G.

Solving NP-hard Problems on Special Instances

Divide and Conquer. Algorithm Fall Semester

Recursive-Fib(n) if n=1 or n=2 then return 1 else return Recursive-Fib(n-1)+Recursive-Fib(n-2)

9. The Disorganized Handyman

Treewidth and graph minors

Examples of P vs NP: More Problems

6. Algorithm Design Techniques

Unit.9 Integer Programming

Lecture Notes for Chapter 2: Getting Started

5. Lecture notes on matroid intersection

Computer Science 385 Design and Analysis of Algorithms Siena College Spring Topic Notes: Dynamic Programming

Data Structures and Algorithms CMPSC 465

P Is Not Equal to NP. ScholarlyCommons. University of Pennsylvania. Jon Freeman University of Pennsylvania. October 1989

Unit-5 Dynamic Programming 2016

Algorithms Exam TIN093/DIT600

CSE 332: Data Structures & Parallelism Lecture 12: Comparison Sorting. Ruth Anderson Winter 2019

Homework Assignment #3. 1 (5 pts) Demonstrate how mergesort works when sorting the following list of numbers:

Greedy Algorithms 1. For large values of d, brute force search is not feasible because there are 2 d

Lesson 2 7 Graph Partitioning

CMSC 754 Computational Geometry 1

Fast algorithms for max independent set

Solving lexicographic multiobjective MIPs with Branch-Cut-Price

Today: Matrix Subarray (Divide & Conquer) Intro to Dynamic Programming (Rod cutting) COSC 581, Algorithms January 21, 2014

Computer Sciences Department 1

Lecture Notes on Liveness Analysis

would be included in is small: to be exact. Thus with probability1, the same partition n+1 n+1 would be produced regardless of whether p is in the inp

1 i n (p i + r n i ) (Note that by allowing i to be n, we handle the case where the rod is not cut at all.)

Financial Optimization ISE 347/447. Lecture 13. Dr. Ted Ralphs

CSE373: Data Structure & Algorithms Lecture 18: Comparison Sorting. Dan Grossman Fall 2013

Decision Problems. Observation: Many polynomial algorithms. Questions: Can we solve all problems in polynomial time? Answer: No, absolutely not.

Notes for Lecture 21. From One-Time Signatures to Fully Secure Signatures

CS125 : Introduction to Computer Science. Lecture Notes #40 Advanced Sorting Analysis. c 2005, 2004 Jason Zych

Design and Analysis of Algorithms

Transcription:

Towards a Memory-Efficient Knapsack DP Algorithm Sanjay Rajopadhye The 0/1 knapsack problem (0/1KP) is a classic problem that arises in computer science. The Wikipedia entry http://en.wikipedia.org/wiki/knapsack_problem has a lot of useful general information. Despite its simplicity (simple formulation and clear algorithms) it is known to be NP-complete, and so the best known algorithms to solve it are have exponential worst case complexity. Nevertheless, common instances of the problem are not difficult to solve. These notes describe the first steps towards the development of a memory efficient dynamic programming (DP) algorithm for the 0/1KP, originally due to Kellerer et. al [1] and independently discovered by Nibbelink et. al [2]. We first recap the basic DP solution to 0/1KP. Problem statement: Given n objects, each one with a weight w i and profit p i, a knapsack of capacity c, choose an subset of the n objects so as to maximize the profit without exceeding the n n capacity, i.e., maximize p i x i subject to the constraints w i x i c. and 1 x i 0. j=1 Since the x i s are binary numbers, there are 2 n candidate solutions. Because the problem is known to be NP-complete, one solution strategy is to exhaustively evaluate each of the candidate solutions. This leads to search based algorithms, and when coupled with techniques for efficiently pruning the search space, yields a family of algorithms called branch-and-bound. DP is an alternative strategy. It is a classic algorithmic technique that can be used to solve many optimization problems other than the knapsack. It exploits recursive structure and relies on decomposing a problem instance into many instances of smaller, simpler subproblems. It usually works in two phases. In the first phase, we first determine the value (or profit) of an j=1 1

optimal solution to the problem, and typically to all subproblems, and store these values in a table. In the second, so called backtracking phase, the actual solution that achieves the optimal profit is chosen by traversing this table of optimal profits. Knapsack subproblems One way of thinking of a smaller instance of a knapsack problem is to have a different capacity: for any integer in the range 1... c, we have an instance of a similar but smaller knapsack problem. Another way is to consider subsets of the objects. For example if we restrict ourselves to just the first i objects, we also have a smaller instance of the problem. Following this idea, we see that we can formulate nc problem instances, one for each pair of integers i, k in the set { i, k 1 i n; 0 k c}. Let us define a function F (i, k) that gives the maximum profit of subproblem i, k, i.e., the maximum profit that can be obtained with capacity k, while choosing from only the first i objects. Exploiting recursive structure of subproblems If we investigate the optimal solution to this subproblem, i, k, there are only two choices vis-à-vis the i-th object: either it is chosen in some optimal solution to the subproblem, or it is not. Let us look at the two cases one by one. Case I: If the i-th object does not contribute to any optimal solution to the problem i, k, then F (i, k) is the same as F (i 1, k), the best solution that can be achieved by simply considering the first i 1 objects at the same capacity. Case II: On the other hand, if the i-th object does contribute to some solution of subproblem i, k, it takes up a capacity of w i and contributes p i to the total profit. Let us keep aside this object and consider how the remaining capacity k w i is filled. By the principle of optimality (subproblems of the optimal solution must also be solved optimally), the profit achieved by this must be F (i 1, k w i ). Hence, for Case II, F (i, k) = p i + F (i 1, k w i ). However, we do not know, a priori, whether or not the i-th object contributes to the solution of subproblem i, k, and we must account for both cases, i.e., F (i, k) is just the larger of the profit from each of the two. Putting this together, and adding boundary conditions (where one 2

or the other subproblems is missing, or has a trivial solution), we get the following recurrence equation. k = 0 0 i = 0 0 F (i, k) = 0 < k < w i ; i > 0 F (i 1, k) F (i 1, k) k w i ; i > 0 max p i + F (i 1, k w i ) (1) Evaluation The above recurrence can be evaluated by allocating an n (c + 1) table an array with n rows and c + 1 columns to store all the values and filling it up in an appropriate order. The order is flexible, as long as we respect the implicit dependences in the computation. Viewing the table as an array (origin at top-left, rows indexed by i, and columns by k) we see that in order to determine F (i, k), we need two entries in the table, both from the previous row: one from directly above and one that is w i to the left. Hence we may fill the table up row-by row or column by column. Also, note that since only the previous row is needed to compute any given row, if we need to compute a single F (i, k) or a row of values, we only need Θ(c) memory we only maintain the previous row in order to compute the current one, and at the end of each row, we swap the previous and current rows. Complete solution However, simply computing the value of F (i, k) is not enough. We need to find the set of objects that make up the optimal solution. We can do this by keeping track of the winner whenever we compared to profits (i.e., the max term in the equation above). In other words, we define a Boolean function B(i, k) to mean the following: B(i, k) = 0 if the i-th object does not contribute to any optimal solution of subproblem i, k, and 1 otherwise (i.e., if there is some solution of subproblem i, k containing the i-th object). 3

Recurrence for B(i, k) k = 0 0 i = 0 0 B(i, k) = 0 < k < w i ; i > 0 0 (2) k w i ; i > 0 if F (i 1, k) > p i + F (i 1, k w i ) then 0 else 1 Once the table of B(i, k) is available, the actual solutions can be easily computed by calling the function mathrmp rintsolution(n 1, c), where if i = 0, k w i print "select i"; return if i = 0, k < w i print "unselect i"; return PrintSolution(i, k) = else if B(i, k) print "select i"; PrintSolution(i 1, k w i ) (3) else print "unselect i"; PrintSolution(i 1, k) It is also possible to not compute B(i, k) explicitly, but save the elements of the table F (i, k),and redo the comparison between the elements during backtracking. Memory Efficient DP The problem with the above algorithm is that in order to compute the final solution, we need to save at least Θ(nc) values (the B table). Therefore, we will derive a memory efficient algorithm that uses only O(c) memory. In this section, we will get you to think about the main ideas needed to develop the memory efficient algorithm. We will not give away the spoiler but leave it to Part II and the next lecture. We encourage you to think about the two main questions that we ask here. The main points we cover are: (i) a naive strategy that recomputes (parts of) the table solves the problem 4

in O(c) memory but adds time complexity, (ii) the outline of a divide-and-conquer strategy that could do better, and (iii) the development of the parameters needed in order for it to succeed. We end with a question that you should try to answer before the next lecture. Suppose that only the last two rows of the F (n, k) table for 0 k c were saved. Then we would only have information to determine whether the last (i.e., the n-th) object contributed to an optimal solution. In addition, we would also be able to decide the residual capacity, c r that the remaining objects took up in an optimal solution. If the n-th object does not contribute to any optimal solution then c r = c, otherwise it is c. Now, a naive strategy is that after determining whether or not the n-th object contributed to an optimal solution, we recurse on the subproblem n 1, c r, i.e., re-compute the table up to the n 1-th row with a capacity of c r. This will solve the problem form the last object down to the first. Since in the worst case, c r can be as large as c, the time complexity for this is, T (n, c) = nc + (n 1)c +... 1 c n = c i 1 2 n2 c (4) i=1 = O(n 2 c) This is not acceptable. We seek a much better algorithm, hopefully one that is only a constant factor slower than the full-table version. Divide-and-conquer is a classic algorithmic technique, and we want to apply it here. For this, we will divide the objects into two disjoint sets, solve the problem on each half and somehow combine the solutions to solve the original problem. The key trick is to decide what it means to solve and to combine. For our problem, solve means determining the last-row of the table in a memory-efficient manner, 1 so the heart of the algorithm is in the combine step. 1 Note that this is not really solving the sub-problem, since we do not still know what choice of objects to pick. In the rest of this discussion, solve in quotes means determining the last-row of a subproblem in a memoryefficient manner, while solv, without the quotes is actually obtaining the solution to the knapsack problem. 5

Because we are going to break up the set of objects, let us modify our notion of knapsack subproblems. Instead of thinking of the i, k -th subproblem, let us think of the i, j, k -th subproblem, where we select from among the i through j objects and optimally try to fill a knapsack of capacity k (so our old i, k -th subproblem is just the 1, i, k -th subproblem in the new notation). Note that the the tables for two subproblems j, k in the pair notation, and i, j, k in the new triple are, in general, completely different, even though they both end at the j-th row. We thus have the outline of our solution first solve, i.e., build the last row of the table for, the subproblems 1, n 2, c and n 2 + 1, n, c. This will need nc time/work. Next we somehow combine this information in such a way that we have two recursive sub-problems to solve. Even before we get to the details of the comine step the pieces and how to put them together let us do an analytical exercise. What should be the size of the subproblems that we will set ourselves to solve recursively? In other words, when would the divide-and-conquer strategy lead to a better algorithm. For example, if each of the two subproblems had a size n c, would this provide any im- 2 provement? To analyze this, let us compute the resulting complexity. We know that with a divide-and-conquer strategy that breaks the number of objects into two equal halves at each step, the number of steps before we get to a singleton object is lg n. Since the total work at each decomposition adds up to nc, the total complexity will be O(nc lg n). Although this is a considerable improvement over the O(n 2 c) of the naive memory-efficient algorithm, it is still worse than the full-table version, so we are not yet done. So the question you need to solve is: what should be the complexity of the two subproblems so that overall, we retain O(nc) complexity. The answer to this will not give us the whole algorithm, but it will guide us to the strategy to follow. Please try to answer this before reading on. 6

The solution Instead of heading straight to the solution, we will think a bit outside the box, and try to just answer the following question: in the optimal solution to the original problem 0, n 1, c how much of the capacity c is taken up by the first half of objects? We use the following steps to answer this question. 1. Partition the objects into two sets (for convenience of the explanation, assume that n is even, say n = 2m). The first set consists of objects 0 through m 1 and the second set consists of objects m through 2m 1. To solve the first problem we will need to consider subproblems 0, j, k for j in the range 0... m 1; for the second one, we need to consider subproblems m, j, k for j in the range m... 2m 1. 2. For the first subproblem, computet 1 (k) the optimal profit that can be achieved for any capacity k between 0 and c. This can be done using the F (i, k) recurrence in a memoryefficient manner (saving only the the most recent row). 3. For the second one too, we can also adapt the F (i, k) recurrence to compute T 2 (k) the optimal profit that can be achieved for subproblem m + 1, 2m, k. Note that T 2 (k) is in general, very different from F (2m, k) the last row of the table in the previous section. This is because although we are still computing something similar to the second half of the old table, we have a very different initialization. Also note that the computations of T 1 (k) and T 2 (k) are completely independent. They can potentially be done in parallel, but this is actually not very profitable. 4. Now, we define a new function P (k) = T 1 (k) + T 2 (c k), for k = 0... c. It can be viewed as the maximum profit which can be achieved for the original problem 1, n, c if we arbitrarily allocated a capacity k to the first half of objects, and the remainder (c k) to the second half. So the value(s) of k where P (k) is the largest (let s call that value 7

c ) is the answer to our question: In an optimal solution to the original problem 1, n, c how much of the capacity c is taken up by the first half of objects? Now that we have solved this out of the box problem, two questions arise. What does it buy us (i.e., how do we use this information to solve the original problem) and at what price (i.e., what does it cost to compute it). To answer the first question, observe that we have only found out where to optimally split the capacity between the two halves of objects, nothing about whether any object is taken or not. However, this assures us that there is an optimal solution to the problem 1, 2m, c in which the first m objects take up c of the total capacity (and the remaining objects take up c c capacity). We can therefore recursively continue this splitting: each step decomposes a problem of the form i, i + 2r, s into two subproblems i, i + r, s and i + r + 1, i + 2r, s such that s = s + s. Eventually when we have only one object, we simply compare the given (optimally split) capacity with the weight of the object to find out whether or not the object contributes to an optimal solution of the original problem 1, 2m, c. And we are done! Note that this solution dos not involve any backtracking, the breaking up a problem into subproblems of the appropriate capacity is leading us to the solution. To answer the second question, notice that the cost to compute the final row T (k) for a subproblem i, j, k is (j i)k, just the size of the table. Hence, the cost of splitting a problem i, i + 2r, s is just (2r + 1)s, where the additional s is for computing P (k). Moreover, the size of one subproblem is r s, and that of the other is r s. Since s = s +s their combined size is r s, exactly half of the size of the original problem. Hence we have the following formula for the total cost of solving the problem: 8

t(m, c) = t(m/2, c) + c(m + 1) = ( m ) c[(m + 1) + 2 + 1 + = c(2m + lg m) ( m 4 + 1 )... (1 + 1)] 2mc Hence, we are doing only twice the work that the original algorithm did, but we are finding the solution with only O(c) memory. Also, notice that the recurrence that we use is almost identical to F (j, k). Main Message: at the heart of the memory efficient algorithm is a function (subroutine) that uses the recurrence F (j, k) except that it only needs to return the last row, and it starts at an arbitrary row, i, assuming that the (i 1)-th row is zero. References [1] Hans Kellerer, Ulrich Pferschy, and David Pisinger. Knapsack Problems, volume ISBN: 978-3-642-07311-3 (Print) 978-3-540-24777-7 (Online). Springer, 2004. [2] K. Nibbelink, S. Rajopadhye, and R. McConnell. 0/1 knapsack on hardware: A complete solution. In ASAP 2007: 18th IEEE International Conference on Application-specific Systems, Architectures and Processors, Montréal, Québec, Canada, 2007. 9