1 00:00:00,040 --> 00:00:02,460 The following content is provided under a Creative 2 00:00:02,460 --> 00:00:03,870 Commons license. 3 00:00:03,870 --> 00:00:06,910 Your support will help MIT OpenCourseWare continue to 4 00:00:06,910 --> 00:00:08,700 offer high quality, educational 5 00:00:08,700 --> 00:00:10,560 resources for free. 6 00:00:10,560 --> 00:00:13,460 To make a donation or view additional materials from 7 00:00:13,460 --> 00:00:19,290 hundreds of MIT courses, visit MIT OpenCourseWare at 8 00:00:19,290 --> 00:00:20,540 ocw.mit.edu. 9 00:00:22,520 --> 00:00:25,820 PROFESSOR: We ended up the last lecture talking about 10 00:00:25,820 --> 00:00:27,800 dynamic programming. 11 00:00:27,800 --> 00:00:30,690 And we'll spend all of today on that topic. 12 00:00:30,690 --> 00:00:34,550 We showed how it could be used to provide a practical 13 00:00:34,550 --> 00:00:40,070 solution to the shortest path problem, a solution that would 14 00:00:40,070 --> 00:00:42,860 allow us to solve what was, in principle, a 15 00:00:42,860 --> 00:00:46,060 complex problem quickly. 16 00:00:46,060 --> 00:00:49,390 And we talked about the properties that make it 17 00:00:49,390 --> 00:00:53,370 possible to solve that problem and, in general, the 18 00:00:53,370 --> 00:00:57,510 properties we need to make dynamic programming 19 00:00:57,510 --> 00:00:59,570 applicable. 20 00:00:59,570 --> 00:01:02,050 So let's return to that topic right now. 21 00:01:08,850 --> 00:01:12,840 What we looked at is we looked at two properties. 22 00:01:12,840 --> 00:01:14,770 The first was optimal substructure. 23 00:01:24,980 --> 00:01:28,440 And you recall what this meant is that we can construct a 24 00:01:28,440 --> 00:01:34,390 globally optimal solution by combining solutions to local 25 00:01:34,390 --> 00:01:36,760 subproblems. 26 00:01:36,760 --> 00:01:39,590 So we've looked at a lot of problems already this term 27 00:01:39,590 --> 00:01:41,830 that have optimal substructure-- 28 00:01:41,830 --> 00:01:46,850 merge sort, for example, which exploits the fact that a list 29 00:01:46,850 --> 00:01:50,940 can be sorted by first sorting components of it and then 30 00:01:50,940 --> 00:01:53,070 combining those. 31 00:01:53,070 --> 00:01:55,916 So merge sort exhibited optimal substructure. 32 00:01:59,410 --> 00:02:03,630 The second property we looked at was overlapping 33 00:02:03,630 --> 00:02:04,880 subproblems. 34 00:02:12,480 --> 00:02:18,340 And what this meant was that in the course of running our 35 00:02:18,340 --> 00:02:22,130 algorithm, we would end up solving the same 36 00:02:22,130 --> 00:02:25,170 problem more than once. 37 00:02:25,170 --> 00:02:31,020 And that is what gave us the power of using memoization, to 38 00:02:31,020 --> 00:02:36,050 use table lookup instead of re-computing the solution. 39 00:02:36,050 --> 00:02:40,210 Notice that merge_sort does not have overlapping 40 00:02:40,210 --> 00:02:42,280 subproblems. 41 00:02:42,280 --> 00:02:45,560 There's no reason that we would expect, if we're sorting 42 00:02:45,560 --> 00:02:48,880 a list, that we would ever encounter the 43 00:02:48,880 --> 00:02:52,510 same sublist twice. 44 00:02:52,510 --> 00:02:56,420 And so in fact, we cannot use dynamic 45 00:02:56,420 --> 00:02:59,630 programming to solve sorting. 46 00:02:59,630 --> 00:03:02,435 Because it has one of these properties, but not both. 47 00:03:05,480 --> 00:03:08,770 So how about shortest path? 48 00:03:08,770 --> 00:03:12,420 Does it have these properties? 49 00:03:12,420 --> 00:03:17,620 Well, let's first think about optimal substructure. 50 00:03:17,620 --> 00:03:21,730 You might begin by asking the question if I knew the 51 00:03:21,730 --> 00:03:27,110 shortest path from A to B, and I knew the shortest path from 52 00:03:27,110 --> 00:03:31,860 B to C, can I necessarily combine those to get the 53 00:03:31,860 --> 00:03:34,175 shortest path from A to C? 54 00:03:37,090 --> 00:03:39,170 No, exactly right. 55 00:03:39,170 --> 00:03:41,850 Because for example, maybe there's a direct link from A 56 00:03:41,850 --> 00:03:48,340 to C. So we can't combine them that way. 57 00:03:48,340 --> 00:03:51,780 So that gives us the question-- 58 00:03:51,780 --> 00:03:54,260 Oh, pardon? 59 00:03:54,260 --> 00:03:54,930 [INAUDIBLE] 60 00:03:54,930 --> 00:03:56,960 PROFESSOR: --Almost got it there. 61 00:03:56,960 --> 00:04:01,180 So that gives us the question, what is the optimal 62 00:04:01,180 --> 00:04:03,460 substructure here? 63 00:04:03,460 --> 00:04:09,160 Well, there is something we do know about shortest path, that 64 00:04:09,160 --> 00:04:16,240 if I have the shortest path from one node to another. 65 00:04:16,240 --> 00:04:22,440 And I take any sub path of that, I will have-- 66 00:04:22,440 --> 00:04:25,920 so let's say I have a path that goes from-- 67 00:04:25,920 --> 00:04:30,421 let's say I know that the shortest path from A to C is A 68 00:04:30,421 --> 00:04:40,808 to B to D to E to C. If this is the shortest path from A to 69 00:04:40,808 --> 00:04:45,450 C, I know that this is the shortest path 70 00:04:45,450 --> 00:04:49,120 from B to E. Right? 71 00:04:49,120 --> 00:04:53,390 Because otherwise, I wouldn't have bothered going through D. 72 00:04:53,390 --> 00:04:57,090 If there were, for example, a link directly from B to E, 73 00:04:57,090 --> 00:04:59,410 then the shortest path from A to C would not 74 00:04:59,410 --> 00:05:00,660 have included this. 75 00:05:03,220 --> 00:05:07,640 So that's the optimal substructure I have, and 76 00:05:07,640 --> 00:05:12,670 that's the substructure that I exploited last time in showing 77 00:05:12,670 --> 00:05:15,900 you the dynamic programming solution to the 78 00:05:15,900 --> 00:05:17,150 shortest path problem. 79 00:05:20,280 --> 00:05:24,120 You also recall that we looked at the shortest path. 80 00:05:24,120 --> 00:05:28,650 We'd found overlapping subproblems. 81 00:05:28,650 --> 00:05:33,270 Because I would end up solving the same intermediate problem 82 00:05:33,270 --> 00:05:36,650 multiple times, figuring out how to get 83 00:05:36,650 --> 00:05:38,640 from one node to another. 84 00:05:38,640 --> 00:05:43,290 So indeed, we saw that the shortest path problem 85 00:05:43,290 --> 00:05:47,270 exhibited both of these properties and therefore, was 86 00:05:47,270 --> 00:05:50,580 amenable to solution by the dynamic programming. 87 00:05:50,580 --> 00:05:54,410 And in fact, we ran it and we saw that it ran quite quickly. 88 00:05:57,850 --> 00:06:03,400 It's not immediately obvious, but the 0-1 knapsack problem 89 00:06:03,400 --> 00:06:05,445 also exhibits both of these properties. 90 00:06:08,310 --> 00:06:13,570 You'll recall a few lectures ago, and also in a problem set 91 00:06:13,570 --> 00:06:20,990 not so long ago, we looked at a recursive solution to the 92 00:06:20,990 --> 00:06:23,892 0-1 knapsack problem. 93 00:06:23,892 --> 00:06:27,730 It's called "solve" in this code. 94 00:06:27,730 --> 00:06:33,760 It uses backtracking to implement the decision tree. 95 00:06:33,760 --> 00:06:36,025 if we look at the decision tree we had there-- 96 00:06:51,080 --> 00:06:53,540 I gave you a small example here of the 97 00:06:53,540 --> 00:06:55,700 0-1 knapsack problem. 98 00:06:55,700 --> 00:06:58,740 I've got A, B, C and D. I've got some values, and I've got 99 00:06:58,740 --> 00:07:01,390 some weights. 100 00:07:01,390 --> 00:07:05,670 We started by saying at the beginning, we looked at this 101 00:07:05,670 --> 00:07:12,240 node here, where we had decided not to take anything . 102 00:07:12,240 --> 00:07:15,360 We still had the list A, B, C, and D to consider. 103 00:07:20,670 --> 00:07:25,950 The total value of our items was 0, and for our example, I 104 00:07:25,950 --> 00:07:29,220 think we had 5 pounds of material left, 105 00:07:29,220 --> 00:07:32,020 or 5 units of weight. 106 00:07:32,020 --> 00:07:34,590 We then considered a decision. 107 00:07:34,590 --> 00:07:38,210 What happens if we take A? 108 00:07:38,210 --> 00:07:43,890 Well, if we take A, we have B, C, and D left to consider. 109 00:07:43,890 --> 00:07:46,550 We now look at this node and say, all right, 110 00:07:46,550 --> 00:07:47,830 our value is 6 -- 111 00:07:47,830 --> 00:07:50,975 the value of A. But we only have 2 pounds left. 112 00:07:53,880 --> 00:07:57,490 We then considered going depth first, left 113 00:07:57,490 --> 00:07:58,860 first in this case. 114 00:07:58,860 --> 00:08:00,340 This is the depth- first search. 115 00:08:00,340 --> 00:08:02,160 We've looked at that before. 116 00:08:02,160 --> 00:08:07,090 All right, let's consider the next branch. 117 00:08:07,090 --> 00:08:16,670 Well the next branch we'll consider C. Well we discover-- 118 00:08:16,670 --> 00:08:18,170 no we'd consider B rather. 119 00:08:18,170 --> 00:08:23,120 We can't take B, because B weighs 3. 120 00:08:23,120 --> 00:08:27,190 So this is a branch that we can't get to. 121 00:08:27,190 --> 00:08:31,230 We can elect the other decision not to take B, in 122 00:08:31,230 --> 00:08:35,620 which case we still have C and D left to consider. 123 00:08:35,620 --> 00:08:38,490 And still 6 and 2. 124 00:08:38,490 --> 00:08:42,330 We then proceed and said right, well can we take C? 125 00:08:42,330 --> 00:08:43,580 Yes we can. 126 00:08:46,050 --> 00:08:49,160 And that leaves us with a value of 14 127 00:08:49,160 --> 00:08:51,650 and no weight left. 128 00:08:51,650 --> 00:08:54,440 So since we have no available weights, we might as well stop 129 00:08:54,440 --> 00:08:56,020 considering things. 130 00:08:56,020 --> 00:08:59,020 So this is a terminal node. 131 00:08:59,020 --> 00:09:02,140 We then back up, and said all right, let's consider the 132 00:09:02,140 --> 00:09:07,990 decision where we don't take C. Can we take D? 133 00:09:07,990 --> 00:09:10,650 And the answer is no because it weighs too much. 134 00:09:10,650 --> 00:09:13,980 And then we're done. 135 00:09:13,980 --> 00:09:17,640 And so we see on this side of the tree, we've explored 136 00:09:17,640 --> 00:09:19,380 various possibilities. 137 00:09:19,380 --> 00:09:21,575 And this is our best option so far. 138 00:09:24,340 --> 00:09:26,860 And we can walk through and now we consider well, what if 139 00:09:26,860 --> 00:09:28,020 we don't take A? 140 00:09:28,020 --> 00:09:32,080 Well then we have these decisions to make. 141 00:09:32,080 --> 00:09:33,710 And eventually, we can look at the whole 142 00:09:33,710 --> 00:09:36,450 thing and make a decision. 143 00:09:36,450 --> 00:09:39,380 By the way, I noticed this morning that there was an 144 00:09:39,380 --> 00:09:42,250 error on your handout. 145 00:09:42,250 --> 00:09:45,100 I chose not to correct it in the slide so that you would 146 00:09:45,100 --> 00:09:47,490 notice it was in your handout as well. 147 00:09:47,490 --> 00:09:49,130 What should node 18 be? 148 00:09:53,530 --> 00:09:54,780 What's the error here? 149 00:09:58,170 --> 00:09:58,455 Pardon? 150 00:09:58,455 --> 00:09:59,705 [INAUDIBLE] 151 00:10:02,880 --> 00:10:03,700 PROFESSOR: Right. 152 00:10:03,700 --> 00:10:05,030 That should be the empty one. 153 00:10:05,030 --> 00:10:05,900 Right? 154 00:10:05,900 --> 00:10:08,142 Because we considered everything. 155 00:10:08,142 --> 00:10:12,170 And in fact, typically all of your bottom nodes should 156 00:10:12,170 --> 00:10:16,910 either have 0 weights or no items left to consider. 157 00:10:16,910 --> 00:10:21,550 Not very deep, just a typo but has the advantage that by 158 00:10:21,550 --> 00:10:23,710 looking for it, you can see whether you understand how 159 00:10:23,710 --> 00:10:26,831 we're constructing these decision trees. 160 00:10:26,831 --> 00:10:29,600 So this is a very common way to deal with an 161 00:10:29,600 --> 00:10:31,690 optimization problem. 162 00:10:31,690 --> 00:10:34,240 You consider all combinations of decisions in 163 00:10:34,240 --> 00:10:35,640 a systematic way. 164 00:10:35,640 --> 00:10:39,260 And when you're done, you choose the best one. 165 00:10:39,260 --> 00:10:43,720 So this is using depth-first search and backtracking. 166 00:10:43,720 --> 00:10:48,350 And this is essentially what you did in your problem set. 167 00:10:48,350 --> 00:10:53,670 Now what you saw in your problem set is that if the set 168 00:10:53,670 --> 00:10:59,240 of items is even moderately large, this algorithm can take 169 00:10:59,240 --> 00:11:03,070 a very long time to run. 170 00:11:03,070 --> 00:11:04,230 How long? 171 00:11:04,230 --> 00:11:08,620 Well, at each level of the tree we're deciding whether to 172 00:11:08,620 --> 00:11:13,700 keep or not keep one item. 173 00:11:13,700 --> 00:11:18,650 So what's the maximum depth of the tree? 174 00:11:18,650 --> 00:11:22,750 How many levels can this tree go? 175 00:11:22,750 --> 00:11:26,200 If we have n items-- 176 00:11:26,200 --> 00:11:26,560 Pardon? 177 00:11:26,560 --> 00:11:27,432 [INAUDIBLE] 178 00:11:27,432 --> 00:11:28,630 PROFESSOR: n levels-- 179 00:11:28,630 --> 00:11:29,800 exactly. 180 00:11:29,800 --> 00:11:32,335 So if we have n items, it can go n levels-- 181 00:11:36,930 --> 00:11:39,040 Good grab. 182 00:11:39,040 --> 00:11:42,710 All right, how many nodes do we have at each level? 183 00:11:42,710 --> 00:11:45,710 Well, at level 0 we have only 1 node. 184 00:11:45,710 --> 00:11:49,470 But what's the down at the bottom? 185 00:11:49,470 --> 00:11:52,350 Let's say we have an enormous amount of weight so that in 186 00:11:52,350 --> 00:11:54,430 fact we never run out of weight in our knapsack. 187 00:11:57,140 --> 00:12:02,650 We'll have quite a broad tree, right? 188 00:12:02,650 --> 00:12:04,220 How many nodes at level 2? 189 00:12:09,920 --> 00:12:10,940 At each level, right? 190 00:12:10,940 --> 00:12:14,430 Down here we have up to 4 nodes, up to 8 191 00:12:14,430 --> 00:12:17,270 nodes, up to 16 nodes. 192 00:12:17,270 --> 00:12:22,920 So if we had say, 39, 40 items, at level 39 we'd have 2 193 00:12:22,920 --> 00:12:25,710 to the 39th nodes-- 194 00:12:25,710 --> 00:12:27,550 pretty darn big tree. 195 00:12:27,550 --> 00:12:28,030 All right? 196 00:12:28,030 --> 00:12:31,810 Not very deep, but very bushy. 197 00:12:31,810 --> 00:12:35,420 So we know that for any reasonable number of items and 198 00:12:35,420 --> 00:12:38,030 a reasonable amount of weight, this is just 199 00:12:38,030 --> 00:12:39,620 not going to work. 200 00:12:39,620 --> 00:12:42,920 And that's why when you wrote your problem set, and you were 201 00:12:42,920 --> 00:12:46,530 trying to solve the optimal set of courses to take, you 202 00:12:46,530 --> 00:12:50,320 could only run it on a small subset of the courses at MIT. 203 00:12:50,320 --> 00:12:52,520 Because if we gave you everything-- and we gave you a 204 00:12:52,520 --> 00:12:53,990 lot for a test-- 205 00:12:53,990 --> 00:12:56,960 you noticed it effectively just wouldn't finish. 206 00:12:59,870 --> 00:13:02,770 So now we have to ask the question well, can we use 207 00:13:02,770 --> 00:13:07,350 dynamic programming to solve this problem? 208 00:13:07,350 --> 00:13:12,320 And in particular, that boils down to the question of does 209 00:13:12,320 --> 00:13:17,360 this solution exhibit optimal substructure and overlapping 210 00:13:17,360 --> 00:13:18,610 subproblems? 211 00:13:22,220 --> 00:13:29,050 Well, optimal substructure is easily visible in both the 212 00:13:29,050 --> 00:13:31,466 decision tree and the code. 213 00:13:31,466 --> 00:13:32,280 Right? 214 00:13:32,280 --> 00:13:35,950 Each parent node combines the solution-- 215 00:13:35,950 --> 00:13:38,940 We can look at the code since you have the 216 00:13:38,940 --> 00:13:41,676 subtree in your handout-- 217 00:13:41,676 --> 00:13:43,650 or the decision tree in your handout. 218 00:13:50,740 --> 00:13:56,570 And the key place in the code is where we combine decisions 219 00:13:56,570 --> 00:14:00,610 from lower down in the tree as we go up. 220 00:14:00,610 --> 00:14:03,310 So at each parent node, we select the 221 00:14:03,310 --> 00:14:04,560 better of the two children. 222 00:14:07,350 --> 00:14:07,780 Right? 223 00:14:07,780 --> 00:14:10,250 If the left child is better than the right child, we 224 00:14:10,250 --> 00:14:11,090 select the left. 225 00:14:11,090 --> 00:14:14,200 Otherwise, we select the right. 226 00:14:14,200 --> 00:14:16,700 And we just percolate up the tree. 227 00:14:16,700 --> 00:14:22,690 So there's clearly optimal substructure here, that we can 228 00:14:22,690 --> 00:14:25,735 solve the higher nodes with solutions to the lower nodes. 229 00:14:28,850 --> 00:14:31,830 And we see that again in the code where it says choose 230 00:14:31,830 --> 00:14:33,080 better branch. 231 00:14:35,910 --> 00:14:40,400 It's less obvious to answer the question whether or not 232 00:14:40,400 --> 00:14:41,960 there are overlapping subproblems. 233 00:14:44,980 --> 00:14:46,850 If we go back and look at the tree-- 234 00:14:56,530 --> 00:15:00,700 At first glance, it may appear that there are no overlapping 235 00:15:00,700 --> 00:15:03,100 subproblems. 236 00:15:03,100 --> 00:15:05,240 You'll notice that at each node we're 237 00:15:05,240 --> 00:15:07,085 solving a different problem. 238 00:15:07,085 --> 00:15:07,620 Right? 239 00:15:07,620 --> 00:15:11,810 The problem is described in some sense by this fortuple. 240 00:15:11,810 --> 00:15:15,220 But if I've already taken A and C, and I have D left to 241 00:15:15,220 --> 00:15:18,200 consider, what should I do? 242 00:15:18,200 --> 00:15:21,190 Where up here I've taken A, and I have B, C, and D left to 243 00:15:21,190 --> 00:15:23,320 consider, what should I do? 244 00:15:23,320 --> 00:15:29,210 And by design of the decision tree, each node is different. 245 00:15:29,210 --> 00:15:29,540 Right? 246 00:15:29,540 --> 00:15:32,350 We're not considering the same thing over and over again. 247 00:15:32,350 --> 00:15:34,900 So you might look at it and say, well, we're out of luck. 248 00:15:34,900 --> 00:15:38,230 There are no overlapping subproblems. 249 00:15:38,230 --> 00:15:40,360 Not true. 250 00:15:40,360 --> 00:15:44,730 So let's think hard for a second and ask in what sense 251 00:15:44,730 --> 00:15:46,590 are there overlapping subproblems? 252 00:15:46,590 --> 00:15:48,830 When are we potentially, at least, 253 00:15:48,830 --> 00:15:50,080 solving the same problem? 254 00:15:54,450 --> 00:15:56,785 Well what is the problem we're actually solving? 255 00:15:59,670 --> 00:16:03,990 We're solving the problem that can in some sense be stated as 256 00:16:03,990 --> 00:16:18,470 follows- at each node, we're saying what should we do about 257 00:16:18,470 --> 00:16:20,430 the nodes left to consider? 258 00:16:25,460 --> 00:16:30,070 So we're solving a problem given a set of items-- 259 00:16:33,430 --> 00:16:35,035 what items we have at a node. 260 00:16:41,080 --> 00:17:02,395 Given an available weight, which items should we take? 261 00:17:14,970 --> 00:17:16,905 What's missing from this? 262 00:17:20,230 --> 00:17:23,170 It says nothing about the items you've already taken. 263 00:17:25,869 --> 00:17:30,050 In order to decide what to take next, we need to know how 264 00:17:30,050 --> 00:17:33,700 much weight we have available. 265 00:17:33,700 --> 00:17:36,680 But we don't need to know why we have that much weight 266 00:17:36,680 --> 00:17:41,680 available, which items we have previously decided to take. 267 00:17:48,782 --> 00:17:53,080 That part of the for tuple turns out not to be relevant 268 00:17:53,080 --> 00:17:55,275 to the problem I still have to solve. 269 00:17:58,700 --> 00:18:01,810 Why is that important? 270 00:18:01,810 --> 00:18:04,800 Because there may be many different sets of items I 271 00:18:04,800 --> 00:18:05,990 could take. 272 00:18:05,990 --> 00:18:09,510 It would add up to the same total weight. 273 00:18:09,510 --> 00:18:13,680 Therefore, leaving me the same problem to solve. 274 00:18:20,300 --> 00:18:23,850 And that's the key observation that tells us we can use 275 00:18:23,850 --> 00:18:27,575 dynamic programming to solve the 0-1 knapsack problem. 276 00:18:30,430 --> 00:18:31,745 Does that-- 277 00:18:31,745 --> 00:18:33,910 I should ask the positive question-- does that make 278 00:18:33,910 --> 00:18:36,390 sense to anybody? 279 00:18:36,390 --> 00:18:37,640 Raise your hand if it makes sense-- 280 00:18:40,550 --> 00:18:41,940 a small number of hands. 281 00:18:41,940 --> 00:18:45,780 Raise your hands if it doesn't make sense. 282 00:18:45,780 --> 00:18:47,360 OK. 283 00:18:47,360 --> 00:18:48,980 Can any of you to whom it doesn't make 284 00:18:48,980 --> 00:18:50,385 sense formulate a question? 285 00:18:53,850 --> 00:18:55,100 No, it's hard. 286 00:18:59,680 --> 00:19:00,630 AUDIENCE: Total weight, or the same total value? 287 00:19:00,630 --> 00:19:04,360 PROFESSOR: The same total weight. 288 00:19:04,360 --> 00:19:05,560 Because-- 289 00:19:05,560 --> 00:19:07,760 the question is are there many sets of items with the same 290 00:19:07,760 --> 00:19:09,950 total value or the same total weight? 291 00:19:09,950 --> 00:19:13,100 There might be many with the same total value, but that 292 00:19:13,100 --> 00:19:15,560 doesn't really matter. 293 00:19:15,560 --> 00:19:20,560 Because in choosing what to do next, as I go down the tree, I 294 00:19:20,560 --> 00:19:24,670 don't care what the value is above the tree. 295 00:19:24,670 --> 00:19:28,130 Because I'll still try and find the best solution I can 296 00:19:28,130 --> 00:19:29,380 to the remaining subproblem. 297 00:19:32,110 --> 00:19:36,830 If a value above is a million, or the value above is 3, it 298 00:19:36,830 --> 00:19:38,960 doesn't matter what I should do next. 299 00:19:44,756 --> 00:19:45,748 AUDIENCE: Which makes sense. 300 00:19:45,748 --> 00:19:48,889 But if you use different things to get that weight, 301 00:19:48,889 --> 00:19:52,210 wouldn't you have a different set of items to choose from? 302 00:19:52,210 --> 00:19:53,110 PROFESSOR: Right. 303 00:19:53,110 --> 00:19:55,610 So the question is it makes sense if there are many 304 00:19:55,610 --> 00:19:57,980 different sets of items that would have the same total 305 00:19:57,980 --> 00:20:00,230 weight, but what I'm going to-- 306 00:20:00,230 --> 00:20:03,550 but, wouldn't I have different items to choose from? 307 00:20:03,550 --> 00:20:06,700 Well you'll notice that I do have to look at what 308 00:20:06,700 --> 00:20:07,990 items I have left. 309 00:20:10,780 --> 00:20:13,810 But as I go through-- 310 00:20:13,810 --> 00:20:21,860 let's assume here that I have a list of items to take. 311 00:20:25,800 --> 00:20:30,760 As I go through from the front, say, I'll label each 312 00:20:30,760 --> 00:20:31,660 node of the tree. 313 00:20:31,660 --> 00:20:35,950 Each item will have either a 0 or a 1, depending upon whether 314 00:20:35,950 --> 00:20:39,800 I decided to take it or not. 315 00:20:39,800 --> 00:20:44,140 And then what I have to consider is 316 00:20:44,140 --> 00:20:45,390 the remaining items. 317 00:20:49,290 --> 00:20:54,410 And let's say the values of the first two items-- 318 00:20:54,410 --> 00:20:56,320 the first four items-- 319 00:20:56,320 --> 00:21:02,340 were, well, let's say they were all one, just to make 320 00:21:02,340 --> 00:21:03,370 life simple. 321 00:21:03,370 --> 00:21:06,420 Well that'll be confusing, since I'm using that for taken 322 00:21:06,420 --> 00:21:07,810 or not taken. 323 00:21:07,810 --> 00:21:09,500 Let's say their values were all 2. 324 00:21:15,980 --> 00:21:19,290 When I get here, and I'm deciding what to do with these 325 00:21:19,290 --> 00:21:25,790 items, I might-- 326 00:21:25,790 --> 00:21:28,170 so this tells me that I've used up 4 327 00:21:28,170 --> 00:21:29,400 pounds of my allotment. 328 00:21:29,400 --> 00:21:31,000 Right? 329 00:21:31,000 --> 00:21:37,630 But if I had done this one, I would also use up 4 pounds of 330 00:21:37,630 --> 00:21:39,420 my allotment. 331 00:21:39,420 --> 00:21:44,800 And so, which of these I take or don't take is independent 332 00:21:44,800 --> 00:21:47,790 of how I got here. 333 00:21:47,790 --> 00:21:50,520 I do have to keep track of which items are still 334 00:21:50,520 --> 00:21:52,240 available to me. 335 00:21:52,240 --> 00:21:55,465 But I don't care how I've used up those 4 pounds. 336 00:22:00,600 --> 00:22:04,380 And obviously, there are a lot of ways to choose two that 337 00:22:04,380 --> 00:22:06,950 will use up 4 pounds here, right? 338 00:22:06,950 --> 00:22:11,060 But the solution to this part of the problem will be 339 00:22:11,060 --> 00:22:15,180 independent of which two I took. 340 00:22:15,180 --> 00:22:17,600 That make sense now? 341 00:22:17,600 --> 00:22:24,950 Therefore, as long as there is a prefix of my list of items, 342 00:22:24,950 --> 00:22:29,440 such that multiple things add up to the same weight, I will 343 00:22:29,440 --> 00:22:31,060 have overlapping subproblems. 344 00:22:34,690 --> 00:22:37,000 Now you could imagine a situation in which no 345 00:22:37,000 --> 00:22:40,770 combination added to the same weight, in which case dynamic 346 00:22:40,770 --> 00:22:43,550 programming wouldn't buy me anything. 347 00:22:43,550 --> 00:22:46,500 It would still find the right answer, but it wouldn't speed 348 00:22:46,500 --> 00:22:49,660 anything up. 349 00:22:49,660 --> 00:22:53,070 But in practice, for most 0-1 knapsack problems-- they may 350 00:22:53,070 --> 00:22:54,660 not be this simple-- 351 00:22:54,660 --> 00:22:56,630 but you could expect-- 352 00:22:56,630 --> 00:22:58,920 and we'll see this complexity later-- 353 00:22:58,920 --> 00:23:03,040 that as long as your possible weights are being chosen from 354 00:23:03,040 --> 00:23:08,070 a relatively small set, you'll have many things add up to the 355 00:23:08,070 --> 00:23:09,120 same thing-- 356 00:23:09,120 --> 00:23:12,050 particularly as you get further down this list and 357 00:23:12,050 --> 00:23:16,480 have a lot longer prefix to consider. 358 00:23:16,480 --> 00:23:22,900 So indeed I do have overlapping subproblems. 359 00:23:22,900 --> 00:23:25,850 That answer the question? 360 00:23:25,850 --> 00:23:28,050 All right, now. 361 00:23:28,050 --> 00:23:31,070 People feel better about what's going on? 362 00:23:31,070 --> 00:23:32,130 OK. 363 00:23:32,130 --> 00:23:37,740 Maybe looking at some code will make it even 364 00:23:37,740 --> 00:23:39,920 clearer, and maybe not. 365 00:23:44,500 --> 00:23:46,400 Anyway, I do appreciate the questions. 366 00:23:46,400 --> 00:23:50,810 Since it's sometimes hard for me to appreciate what's coming 367 00:23:50,810 --> 00:23:53,273 across and what's not coming across. 368 00:23:53,273 --> 00:23:56,070 You should get rewarded for good questions, as well as 369 00:23:56,070 --> 00:23:58,530 good answers. 370 00:23:58,530 --> 00:24:01,880 All right, so now let's look at a 371 00:24:01,880 --> 00:24:06,803 dynamic programming solution. 372 00:24:10,120 --> 00:24:13,015 I've just taken the previously-- 373 00:24:13,015 --> 00:24:16,540 the example we looked at before was solve-- 374 00:24:16,540 --> 00:24:18,700 made it fast solve. 375 00:24:18,700 --> 00:24:22,550 And I've added a parameter of memo. 376 00:24:22,550 --> 00:24:26,330 This is the same kind of trick we used last time for our 377 00:24:26,330 --> 00:24:29,330 shortest path problem. 378 00:24:29,330 --> 00:24:32,460 I should point out that I'm using-- 379 00:24:32,460 --> 00:24:37,350 this calls to mind a dirty, little secret of Python, one I 380 00:24:37,350 --> 00:24:41,960 have previously been hiding because it's so ugly I just 381 00:24:41,960 --> 00:24:43,490 hate to talk about it. 382 00:24:43,490 --> 00:24:46,840 But I figured at some point, honesty is the right policy. 383 00:24:50,090 --> 00:24:55,820 What I'd like is the first time I call fast solve, I 384 00:24:55,820 --> 00:24:59,660 shouldn't have to know that there's even a memo. 385 00:24:59,660 --> 00:25:06,910 Fast solve should have the same interface as solve. 386 00:25:06,910 --> 00:25:09,540 The items to consider and the available weight, and that's 387 00:25:09,540 --> 00:25:11,490 all you should need to know. 388 00:25:11,490 --> 00:25:15,830 Because the memo is part of the implementation, not part 389 00:25:15,830 --> 00:25:18,510 of the specification of solving the 390 00:25:18,510 --> 00:25:20,690 0-1 knapsack problem. 391 00:25:20,690 --> 00:25:22,910 We've made that argument several times, 392 00:25:22,910 --> 00:25:25,710 earlier in the semester. 393 00:25:25,710 --> 00:25:34,370 And so you might think that what I should therefore do is 394 00:25:34,370 --> 00:25:38,920 to initialize the memo to, say the empty dictionary. 395 00:25:38,920 --> 00:25:43,150 I'm going to use dictionaries for memos, as before. 396 00:25:43,150 --> 00:25:46,140 And then just check and if it's the empty one, that means 397 00:25:46,140 --> 00:25:47,870 it's the first-- 398 00:25:47,870 --> 00:25:50,500 I can know whether it's the-- it'll work fine 399 00:25:50,500 --> 00:25:51,980 for the first call. 400 00:25:51,980 --> 00:25:54,710 Well it doesn't work fine. 401 00:25:54,710 --> 00:25:57,200 And here's the dirty, little secret. 402 00:25:57,200 --> 00:26:02,680 In Python, when you have a parameter with a default 403 00:26:02,680 --> 00:26:07,915 value, that default value is evaluated once. 404 00:26:10,750 --> 00:26:15,720 So the way the Python system works is it will process all 405 00:26:15,720 --> 00:26:22,020 of the def statements and evaluate the 406 00:26:22,020 --> 00:26:25,640 right-hand side here once. 407 00:26:25,640 --> 00:26:30,630 And then every time the function is called, without 408 00:26:30,630 --> 00:26:33,700 this optional argument, it will use the value it found 409 00:26:33,700 --> 00:26:36,790 when it evaluated the def statement. 410 00:26:36,790 --> 00:26:44,790 Now if this value is immutable, it doesn't matter. 411 00:26:44,790 --> 00:26:46,970 None will be none forever. 412 00:26:46,970 --> 00:26:48,830 28 will be 28 forever. 413 00:26:48,830 --> 00:26:52,780 3.7 will be 3.7 forever. 414 00:26:52,780 --> 00:26:59,030 But if this is a mutable value, for example a dict, 415 00:26:59,030 --> 00:27:05,330 then what will happen is it will create an object which 416 00:27:05,330 --> 00:27:09,150 will be initially an empty dictionary. 417 00:27:09,150 --> 00:27:14,510 But then every time fast solve is called without this 418 00:27:14,510 --> 00:27:18,865 argument, it will access the same object. 419 00:27:22,600 --> 00:27:27,420 So if I call fast solve to solve one problem, and in the 420 00:27:27,420 --> 00:27:31,910 course of that, it builds up a big dictionary. 421 00:27:31,910 --> 00:27:36,460 And then I call it again to solve another problem, instead 422 00:27:36,460 --> 00:27:41,280 of starting with the empty dictionary, it will start with 423 00:27:41,280 --> 00:27:44,150 the same object it started with the first time, which is 424 00:27:44,150 --> 00:27:46,150 by now a dictionary filled with values. 425 00:27:49,430 --> 00:27:51,275 And so I will get the wrong answer. 426 00:27:54,100 --> 00:28:00,020 This is a subtlety, and it's a common kind of bug in Python. 427 00:28:00,020 --> 00:28:03,670 And I confess to having been bitten by it very recently, as 428 00:28:03,670 --> 00:28:08,050 in yesterday, myself. 429 00:28:08,050 --> 00:28:09,240 So it's worth remembering. 430 00:28:09,240 --> 00:28:13,400 And there's a simple workaround, which is the 431 00:28:13,400 --> 00:28:15,840 default value is the immutable value. 432 00:28:15,840 --> 00:28:17,550 I chose none. 433 00:28:17,550 --> 00:28:21,290 And what I say when I enter it is if the memo is none, it 434 00:28:21,290 --> 00:28:22,460 means it's been invoked. 435 00:28:22,460 --> 00:28:27,580 And now I'll initialize it to the empty dictionary. 436 00:28:27,580 --> 00:28:32,060 And now every time it will get a new one, because we know 437 00:28:32,060 --> 00:28:35,790 what this statement says is allocate a new object of type 438 00:28:35,790 --> 00:28:39,700 dict, and initialize it to empty. 439 00:28:39,700 --> 00:28:42,880 This will happen dynamically when the thing is invoked, 440 00:28:42,880 --> 00:28:46,040 rather than statically at the time Python 441 00:28:46,040 --> 00:28:48,700 processes the diffs. 442 00:28:48,700 --> 00:28:50,280 It's a silly little problem. 443 00:28:50,280 --> 00:28:51,820 I hate to bring it up. 444 00:28:51,820 --> 00:28:54,670 I don't think it should work that way, but it 445 00:28:54,670 --> 00:28:55,660 does work that way. 446 00:28:55,660 --> 00:28:56,610 So we're stuck. 447 00:28:56,610 --> 00:28:56,865 Yeah. 448 00:28:56,865 --> 00:28:58,115 [INAUDIBLE] 449 00:28:59,772 --> 00:29:00,730 PROFESSOR: I'm sorry. 450 00:29:00,730 --> 00:29:01,680 You've have to speak more loudly. 451 00:29:01,680 --> 00:29:02,930 [INAUDIBLE] 452 00:29:06,993 --> 00:29:10,870 PROFESSOR: Well, why doesn't my first call-- 453 00:29:10,870 --> 00:29:15,890 So the question is when I go to test it, say, why don't I 454 00:29:15,890 --> 00:29:20,380 start by calling it with fast solve in an empty dictionary? 455 00:29:20,380 --> 00:29:28,170 And it's because, as we'll see when I get there, I want solve 456 00:29:28,170 --> 00:29:37,980 and fast solve, in this case, to have the same interface. 457 00:29:37,980 --> 00:29:41,250 And in fact, you can say I want them to have the same 458 00:29:41,250 --> 00:29:42,500 specification. 459 00:29:46,970 --> 00:29:52,340 Because in fact, imagine that you wrote a program that 460 00:29:52,340 --> 00:29:55,070 called solve many times. 461 00:29:55,070 --> 00:29:58,120 And it was slow, and then you took 6.00-- 462 00:29:58,120 --> 00:30:00,250 So, I know why it's slow, because I've 463 00:30:00,250 --> 00:30:02,060 used a stupid solve. 464 00:30:02,060 --> 00:30:05,270 Let me use dynamic programming. 465 00:30:05,270 --> 00:30:09,770 I want to then be able to not name the new one fast solve, 466 00:30:09,770 --> 00:30:13,380 but replace the old solve by the new solve. 467 00:30:13,380 --> 00:30:17,990 And have all the programs that you solve still work. 468 00:30:17,990 --> 00:30:21,450 That will not happen if I insist that the new solve has 469 00:30:21,450 --> 00:30:22,700 an extra argument. 470 00:30:25,410 --> 00:30:29,395 Now I could put what's called a wrapper in and call solve, 471 00:30:29,395 --> 00:30:34,295 and then have it call fast solve with the extra argument. 472 00:30:34,295 --> 00:30:36,060 And that would work too. 473 00:30:36,060 --> 00:30:40,230 That would be an equally good solution to 474 00:30:40,230 --> 00:30:43,710 address this problem. 475 00:30:43,710 --> 00:30:47,630 But does that make sense? 476 00:30:47,630 --> 00:30:50,020 So either one would work. 477 00:30:50,020 --> 00:30:54,740 I chose this one but what wouldn't work in a practical 478 00:30:54,740 --> 00:30:58,630 engineering sense is to change the specification of solve. 479 00:31:01,850 --> 00:31:03,840 All right? 480 00:31:03,840 --> 00:31:08,020 So it's a silly little thing, but since people do get bitten 481 00:31:08,020 --> 00:31:13,380 by it, I figure I should tell you about it. 482 00:31:13,380 --> 00:31:15,870 And I guess while I'm on the subject of silly little 483 00:31:15,870 --> 00:31:19,170 things, I'll tell you one more thing before we go back to 484 00:31:19,170 --> 00:31:21,570 this algorithm. 485 00:31:21,570 --> 00:31:28,550 Down here in the place where I'm testing it, you'll note 486 00:31:28,550 --> 00:31:32,530 that I've imported something called sys, and then said 487 00:31:32,530 --> 00:31:38,040 sys.setrecursionlimit to 2000. 488 00:31:38,040 --> 00:31:43,410 In Python, there's a maximum depth of recursion. 489 00:31:43,410 --> 00:31:46,980 When this is exceeded, it raises an exception-- 490 00:31:46,980 --> 00:31:49,290 depth exceeded. 491 00:31:49,290 --> 00:31:51,730 As it happens, the default value for that 492 00:31:51,730 --> 00:31:53,040 is some tiny number-- 493 00:31:53,040 --> 00:31:56,090 I forget what it is-- 494 00:31:56,090 --> 00:31:59,520 such that when I run this on an interesting size example, 495 00:31:59,520 --> 00:32:02,740 it crashes that exception. 496 00:32:02,740 --> 00:32:05,070 So all I'm doing here is saying no, I 497 00:32:05,070 --> 00:32:06,390 know what I'm doing. 498 00:32:06,390 --> 00:32:09,620 It's OK to recurse to a greater depth. 499 00:32:09,620 --> 00:32:12,270 And I've set it to 2000 here, which is 500 00:32:12,270 --> 00:32:14,050 plenty for what I need. 501 00:32:14,050 --> 00:32:15,390 I could set it to 20,000. 502 00:32:15,390 --> 00:32:18,420 I could set it to 50,000. 503 00:32:18,420 --> 00:32:21,940 Again, if you're writing a serious program, you'll 504 00:32:21,940 --> 00:32:24,490 probably find that the default value for the depth of 505 00:32:24,490 --> 00:32:29,410 recursion is not enough, and you might want to reset it. 506 00:32:29,410 --> 00:32:31,990 I don't expect you to remember how to do this. 507 00:32:31,990 --> 00:32:34,540 I expect you to remember that it's possible. 508 00:32:34,540 --> 00:32:37,460 And if you need to do it, you can Google "recursion limit 509 00:32:37,460 --> 00:32:41,350 Python" and it will tell you how to change it. 510 00:32:41,350 --> 00:32:43,790 But again, I've found in testing this, the 511 00:32:43,790 --> 00:32:45,455 default was too small. 512 00:32:52,520 --> 00:32:52,670 Ok. 513 00:32:52,670 --> 00:32:55,655 Let's go look at the code now for fast solve. 514 00:33:05,040 --> 00:33:07,860 It's got the items to consider, what's available, 515 00:33:07,860 --> 00:33:12,660 the available weight, and a memo. 516 00:33:12,660 --> 00:33:15,440 And I'm going to keep track of the number of calls. 517 00:33:15,440 --> 00:33:17,330 Just for pedagogical reasons, we'll want 518 00:33:17,330 --> 00:33:18,580 to review that later. 519 00:33:27,640 --> 00:33:30,360 OK, so I'll initialize the memo if it's 520 00:33:30,360 --> 00:33:31,610 the first time through. 521 00:33:33,940 --> 00:33:40,580 And then the first thing I'll do is I'm going to code what I 522 00:33:40,580 --> 00:33:45,520 have available essentially the way I've coded it here-- so 523 00:33:45,520 --> 00:33:50,850 what items are remaining by using an index. 524 00:33:50,850 --> 00:33:54,080 So my items are going to be a set of lists. 525 00:33:54,080 --> 00:33:55,910 And I'm going to just sort of keep track of 526 00:33:55,910 --> 00:33:57,310 where I am in the list-- 527 00:34:00,890 --> 00:34:05,510 just march through exactly the way I'd marched through here. 528 00:34:05,510 --> 00:34:10,380 And so if the list of things to consider, 529 00:34:10,380 --> 00:34:12,380 the length of it-- 530 00:34:12,380 --> 00:34:14,560 because every time the length is the same, it 531 00:34:14,560 --> 00:34:16,239 will be the same list. 532 00:34:16,239 --> 00:34:19,690 Since I'm systematically examining a prefix. 533 00:34:19,690 --> 00:34:22,940 I'm not shuffling the list each time. 534 00:34:22,940 --> 00:34:26,250 So the length can be used to tell me what I've 535 00:34:26,250 --> 00:34:28,420 already looked at. 536 00:34:28,420 --> 00:34:33,139 So if I've looked at that sublist before, with this 537 00:34:33,139 --> 00:34:37,150 available weight, it will be in the memo. 538 00:34:37,150 --> 00:34:39,659 And I'll just look up the solution-- 539 00:34:39,659 --> 00:34:41,480 result equals memo of [UNINTELLIGIBLE] 540 00:34:41,480 --> 00:34:43,980 to consider avail. 541 00:34:43,980 --> 00:34:50,560 So those will be my keys for my dictionary. 542 00:34:50,560 --> 00:34:54,520 The key will be a pair of essentially which items I have 543 00:34:54,520 --> 00:35:00,900 left to consider, which is coded by a number, and the 544 00:35:00,900 --> 00:35:04,480 amount of weight, another number. 545 00:35:04,480 --> 00:35:09,190 Or if there's nothing left to consider, or there's no weight 546 00:35:09,190 --> 00:35:16,650 available, then I'll return 0 and the empty tuple-- 547 00:35:16,650 --> 00:35:18,595 no value, didn't take anything. 548 00:35:21,220 --> 00:35:23,810 Otherwise, I'm now in the interesting case. 549 00:35:28,470 --> 00:35:36,690 If to consider subzero, the first one here, if the weight 550 00:35:36,690 --> 00:35:44,990 of that is greater than what I have available, then I know I 551 00:35:44,990 --> 00:35:45,810 can't take it. 552 00:35:45,810 --> 00:35:47,850 Right? 553 00:35:47,850 --> 00:35:54,050 So I'll just lop it off and call fast solve recursively 554 00:35:54,050 --> 00:35:57,300 with the remaining list-- 555 00:35:57,300 --> 00:36:00,120 same old value of avail and the same old memo. 556 00:36:03,920 --> 00:36:07,850 Otherwise, well now I have an option of taking the first 557 00:36:07,850 --> 00:36:10,130 element in the list. 558 00:36:10,130 --> 00:36:13,370 I'll set the item to that element, and I'll 559 00:36:13,370 --> 00:36:16,280 consider taking it. 560 00:36:16,280 --> 00:36:23,040 So I'll call fast solve with to consider 561 00:36:23,040 --> 00:36:26,050 without the first item. 562 00:36:26,050 --> 00:36:30,570 But the amount of weight will be available now is what was 563 00:36:30,570 --> 00:36:34,860 available before minus the weight of that item. 564 00:36:34,860 --> 00:36:37,000 This is my left branch, if you will, where I 565 00:36:37,000 --> 00:36:39,480 decide to take the item. 566 00:36:39,480 --> 00:36:43,280 And so now I'm solving a smaller problem. 567 00:36:43,280 --> 00:36:45,580 The list is smaller, and I have less weight. 568 00:36:49,280 --> 00:36:55,440 The next thing I'll do is consider not taking the item. 569 00:36:55,440 --> 00:36:59,060 So I'll call fast solve again with the 570 00:36:59,060 --> 00:37:00,740 remainder of the list. 571 00:37:00,740 --> 00:37:03,900 But avail and memo are not changed. 572 00:37:03,900 --> 00:37:05,610 That's the right branch. 573 00:37:05,610 --> 00:37:08,500 And you'll remember our decision tree the right branch 574 00:37:08,500 --> 00:37:11,970 avail and weight were never changed. 575 00:37:11,970 --> 00:37:15,620 Because I elected not to take that item. 576 00:37:15,620 --> 00:37:17,330 Then I'll just choose the better of the two 577 00:37:17,330 --> 00:37:18,580 alternatives, as before. 578 00:37:22,090 --> 00:37:27,420 And when I'm done, I'll update the memo with the solution to 579 00:37:27,420 --> 00:37:31,450 the problem I just solved. 580 00:37:31,450 --> 00:37:33,190 All right? 581 00:37:33,190 --> 00:37:38,740 So it's exactly the same as the recursive solution you 582 00:37:38,740 --> 00:37:41,190 looked at a few weeks ago. 583 00:37:41,190 --> 00:37:46,620 Except I've added this notion of a memo to keep track of 584 00:37:46,620 --> 00:37:49,220 what we've already done. 585 00:37:49,220 --> 00:37:50,695 Well let's see how well it works. 586 00:37:53,200 --> 00:37:55,270 So I've got this test program. 587 00:37:58,930 --> 00:38:04,240 Just so things are repeatable, I'd set the C to 0 -- 588 00:38:04,240 --> 00:38:07,810 doesn't matter what I set it to, this just says instead of 589 00:38:07,810 --> 00:38:12,540 getting a random value each time, I'll get the same C so 590 00:38:12,540 --> 00:38:16,520 I'll get the same sequence of pseudo-random numbers-- 591 00:38:16,520 --> 00:38:17,770 makes it easier to debug. 592 00:38:20,980 --> 00:38:23,660 I'll have this global variable num calls. 593 00:38:23,660 --> 00:38:27,260 I'm arbitrarily setting the capacity to eight times the 594 00:38:27,260 --> 00:38:28,960 maximum weight. 595 00:38:28,960 --> 00:38:31,810 So I'm saying the maximum value of an item is 10. 596 00:38:31,810 --> 00:38:34,820 The maximum weight is 10. 597 00:38:34,820 --> 00:38:41,020 This just says runs slowly is false, as in don't run the 598 00:38:41,020 --> 00:38:41,820 slow version. 599 00:38:41,820 --> 00:38:43,990 Run only the fast version. 600 00:38:43,990 --> 00:38:45,510 We'll come back to that. 601 00:38:45,510 --> 00:38:47,740 I set the capacity of the Knapsack to 8 602 00:38:47,740 --> 00:38:50,950 times the max weight. 603 00:38:50,950 --> 00:38:53,140 And then for the number of items-- and I'm just going to 604 00:38:53,140 --> 00:38:55,080 go through a different number of items, 605 00:38:55,080 --> 00:38:58,930 4, 8 16, up to 1024-- 606 00:38:58,930 --> 00:39:01,660 I'm going to call build many items. 607 00:39:01,660 --> 00:39:03,590 Again, we've seen this program before. 608 00:39:03,590 --> 00:39:06,340 That just takes a number of items, a maximum value, and a 609 00:39:06,340 --> 00:39:11,430 maximum weight, and builds some set of items, choosing 610 00:39:11,430 --> 00:39:15,670 the values and weights at random from the ones we've 611 00:39:15,670 --> 00:39:17,760 offered it. 612 00:39:17,760 --> 00:39:18,550 This saves me. 613 00:39:18,550 --> 00:39:21,360 I wasn't going to type a set of 1024 items, I 614 00:39:21,360 --> 00:39:22,610 guarantee you that. 615 00:39:25,030 --> 00:39:30,290 If it runs slowly, then I'm going to test it on both fast 616 00:39:30,290 --> 00:39:32,130 solve and solve. 617 00:39:32,130 --> 00:39:36,390 Notice that these are the names of the functions, not 618 00:39:36,390 --> 00:39:38,420 invocations of the functions. 619 00:39:38,420 --> 00:39:40,850 Because remember functions, like everything else in 620 00:39:40,850 --> 00:39:43,640 Python, are just objects. 621 00:39:43,640 --> 00:39:49,450 Otherwise, my tests will be only the tuple fast solve. 622 00:39:49,450 --> 00:39:52,930 And then for each function in tests, I'm going to set the 623 00:39:52,930 --> 00:39:54,530 number of calls to 0. 624 00:39:54,530 --> 00:39:57,920 I'm going to load up a timer, just because I'm curious how 625 00:39:57,920 --> 00:39:59,790 long it takes. 626 00:39:59,790 --> 00:40:02,340 And then I'll call the func. 627 00:40:02,340 --> 00:40:06,570 Notice again, getting back to what we talked about before, 628 00:40:06,570 --> 00:40:10,420 both fast solve and solve get called 629 00:40:10,420 --> 00:40:12,690 with the same arguments. 630 00:40:12,690 --> 00:40:16,290 I could not have done this trick if fast solve had 631 00:40:16,290 --> 00:40:17,550 required an extra parameter. 632 00:40:20,620 --> 00:40:25,340 And then I'll just see how well it did. 633 00:40:25,340 --> 00:40:36,070 So let's start with this equal to true. 634 00:40:36,070 --> 00:40:38,070 So in that case we'll test both of them. 635 00:40:38,070 --> 00:40:39,320 And let's see how we do. 636 00:40:49,500 --> 00:40:50,750 So it's chugging along. 637 00:40:53,250 --> 00:40:57,980 And we see that if I have 4 items, fast solve made 29 638 00:40:57,980 --> 00:41:01,350 calls, took this much time. 639 00:41:01,350 --> 00:41:03,740 The good news, by the way, you'll notice that fast solve 640 00:41:03,740 --> 00:41:08,890 and solve gave me the same value each time. 641 00:41:08,890 --> 00:41:11,530 We would hope so, right? 642 00:41:11,530 --> 00:41:14,640 Because they're both supposed to find an optimal solution. 643 00:41:14,640 --> 00:41:16,700 They could find different sets of items. 644 00:41:16,700 --> 00:41:17,890 That would be OK. 645 00:41:17,890 --> 00:41:19,680 But they better add up to the same value. 646 00:41:25,240 --> 00:41:27,710 But we seem to be stuck here. 647 00:41:27,710 --> 00:41:32,870 So you'll notice at 16, Solve made 131,000 648 00:41:32,870 --> 00:41:36,420 calls, fast solve 3,500-- 649 00:41:36,420 --> 00:41:38,440 only took a third of a second. 650 00:41:38,440 --> 00:41:41,310 But we seem to be kind of stuck at-- 651 00:41:41,310 --> 00:41:44,710 actually, fast solve only made 1,200. 652 00:41:44,710 --> 00:41:48,830 At 32, fast solve made 3,500 calls. 653 00:41:48,830 --> 00:41:50,920 But solve seems to be stuck. 654 00:41:53,920 --> 00:41:57,290 Should this be a shocker that going from 16 to 32 made a big 655 00:41:57,290 --> 00:41:58,900 difference? 656 00:41:58,900 --> 00:42:03,860 Well remember here we're talking about 2 to the 16 657 00:42:03,860 --> 00:42:08,220 possibilities to investigate, which is not a huge number. 658 00:42:08,220 --> 00:42:13,060 But now we're at 2 to the 32, which is quite a large number. 659 00:42:13,060 --> 00:42:18,670 And so we could wait quite a long time for solve to 660 00:42:18,670 --> 00:42:21,780 actually finish. 661 00:42:21,780 --> 00:42:25,290 And in fact, we're not going to wait that long. 662 00:42:25,290 --> 00:42:27,720 We'll interrupt it. 663 00:42:27,720 --> 00:42:31,615 So let's see how well fast solve works. 664 00:42:41,930 --> 00:42:43,190 We just call that. 665 00:42:43,190 --> 00:42:45,215 So we'll set this to false. 666 00:42:45,215 --> 00:42:47,060 It says don't run slowly, i.e. 667 00:42:47,060 --> 00:42:48,310 Don't call solve. 668 00:43:00,200 --> 00:43:05,450 So not only did we not get stuck at 32 items way up here, 669 00:43:05,450 --> 00:43:10,650 we didn't get stuck at 1024 items. 670 00:43:10,650 --> 00:43:12,180 Huge difference, right? 671 00:43:12,180 --> 00:43:16,680 Instead of only being able to process a set of 16, we can 672 00:43:16,680 --> 00:43:19,090 process a set of over 1,000. 673 00:43:19,090 --> 00:43:21,160 And in fact, I don't know how much higher we can go. 674 00:43:21,160 --> 00:43:23,770 I didn't try anything bigger. 675 00:43:23,770 --> 00:43:24,830 But even this-- 676 00:43:24,830 --> 00:43:27,690 big difference between getting stuck here and not getting 677 00:43:27,690 --> 00:43:30,490 stuck here. 678 00:43:30,490 --> 00:43:32,360 And in fact, not only did not get stuck, it 679 00:43:32,360 --> 00:43:36,350 was only two seconds. 680 00:43:36,350 --> 00:43:38,290 So let's ask ourselves the question-- how 681 00:43:38,290 --> 00:43:39,580 fast is this growing? 682 00:43:44,170 --> 00:43:46,220 Not very fast-- 683 00:43:46,220 --> 00:43:49,410 more or less, what we see here-- and this is not 684 00:43:49,410 --> 00:43:51,540 something we can guarantee. 685 00:43:51,540 --> 00:43:55,290 But in this case, what we see is as we double the number of 686 00:43:55,290 --> 00:44:01,000 items, the number of calls kind of doubles as well. 687 00:44:05,510 --> 00:44:07,520 So we're actually growing pretty slowly. 688 00:44:13,080 --> 00:44:16,400 Well, OK that's good. 689 00:44:16,400 --> 00:44:20,360 But suppose we want to look at it a little bit more carefully 690 00:44:20,360 --> 00:44:25,370 and consider the question of what does govern the running 691 00:44:25,370 --> 00:44:26,620 time of fast solve. 692 00:44:30,640 --> 00:44:34,690 Well we know that looking things up in the dict is 693 00:44:34,690 --> 00:44:37,270 constant time. 694 00:44:37,270 --> 00:44:40,250 So that's OK. 695 00:44:40,250 --> 00:44:45,850 So what's going to govern it is every time we have to add 696 00:44:45,850 --> 00:44:49,400 an element to the memo, it's going to slow us down. 697 00:44:49,400 --> 00:44:52,250 Because you're gonna have to solve a problem. 698 00:44:52,250 --> 00:44:55,690 Every time we can just look something up in the memo, 699 00:44:55,690 --> 00:44:57,445 we'll get our answer back instantaneously. 700 00:44:59,960 --> 00:45:03,060 So if we think about the running time, it's going to be 701 00:45:03,060 --> 00:45:11,110 related to how many different key value pairs might we have 702 00:45:11,110 --> 00:45:14,630 to compute for the memo. 703 00:45:14,630 --> 00:45:16,680 Well what governs that? 704 00:45:16,680 --> 00:45:23,390 So we know that the keys in the memo are pairs of to 705 00:45:23,390 --> 00:45:29,040 consider and avail. 706 00:45:33,520 --> 00:45:41,300 And so we know that the number of possible different keys 707 00:45:41,300 --> 00:45:44,960 will be the number of possible values in to consider, 708 00:45:44,960 --> 00:45:50,400 multiplied by the number of possible values of avail. 709 00:45:50,400 --> 00:45:51,650 Right? 710 00:45:53,400 --> 00:45:56,150 How many possible values of to consider are there? 711 00:46:00,410 --> 00:46:03,530 What governs that? 712 00:46:03,530 --> 00:46:06,830 That's the easy question. 713 00:46:06,830 --> 00:46:09,420 Number of items, right? 714 00:46:16,780 --> 00:46:21,280 Because I'm just marching across this list, chopping off 715 00:46:21,280 --> 00:46:23,200 one item at a time. 716 00:46:23,200 --> 00:46:26,510 At most I can chop off n items, if the list 717 00:46:26,510 --> 00:46:27,760 is of length n. 718 00:46:31,920 --> 00:46:35,270 Avail is more complicated. 719 00:46:35,270 --> 00:46:36,890 What does avail depend upon? 720 00:46:42,080 --> 00:46:47,950 It depends upon, well, the initial available weight. 721 00:46:50,578 --> 00:46:55,000 If the initial available weight is 0, well then it's 722 00:46:55,000 --> 00:46:57,340 gonna end really quickly. 723 00:46:57,340 --> 00:47:04,640 If it's really big, I might have to churn longer. 724 00:47:04,640 --> 00:47:07,160 But it also depends-- 725 00:47:07,160 --> 00:47:09,860 and this is the key thing-- 726 00:47:09,860 --> 00:47:33,470 on the number of different weights, that sets of items 727 00:47:33,470 --> 00:47:34,720 can add up to. 728 00:47:43,310 --> 00:47:46,950 Because you'll recall the whole secret here was that we 729 00:47:46,950 --> 00:47:50,770 said that different sets of items could actually have the 730 00:47:50,770 --> 00:47:52,270 same total weight. 731 00:47:52,270 --> 00:47:57,390 And that's why we had this business of overlapping 732 00:47:57,390 --> 00:47:58,640 subproblems. 733 00:48:00,860 --> 00:48:08,660 Now here, since I only had 10 possible weights, that tells 734 00:48:08,660 --> 00:48:11,240 me that I can't have very many different combinations. 735 00:48:13,800 --> 00:48:14,960 Right? 736 00:48:14,960 --> 00:48:21,510 That any set of say, one item has to be 0 through 10, or 0 737 00:48:21,510 --> 00:48:24,970 through 9, I forget which it was. 738 00:48:24,970 --> 00:48:29,370 But either way, that means that alI only have 10 739 00:48:29,370 --> 00:48:36,690 different possible sums for sets of length 1. 740 00:48:36,690 --> 00:48:40,270 Whereas if my weights ranged over 100, I would have 100 741 00:48:40,270 --> 00:48:45,000 different possibilities for sets of length 1. 742 00:48:45,000 --> 00:48:49,410 So that's an important factor in governing it. 743 00:48:59,000 --> 00:49:03,620 So it's a complicated situation, but just to see 744 00:49:03,620 --> 00:49:09,110 what happens, try and remember how long this took for 1024. 745 00:49:09,110 --> 00:49:11,600 It took 2.2 seconds. 746 00:49:11,600 --> 00:49:14,036 I'm going to allow 20 different weights. 747 00:49:17,440 --> 00:49:18,690 And let's see what happens. 748 00:49:31,640 --> 00:49:34,020 It took roughly twice as long. 749 00:49:38,650 --> 00:49:40,880 So that's sort of what we would have expected. 750 00:49:40,880 --> 00:49:43,640 So again, we're going to see the running time 751 00:49:43,640 --> 00:49:47,770 is related to that. 752 00:49:47,770 --> 00:49:49,340 So we can see that it's actually a 753 00:49:49,340 --> 00:49:51,650 fairly complicated thing. 754 00:49:51,650 --> 00:50:02,680 Suppose I did something really nasty and went up here where I 755 00:50:02,680 --> 00:50:03,930 built my items-- 756 00:50:16,390 --> 00:50:19,890 so here you'll see, when I built my items I chose the 757 00:50:19,890 --> 00:50:21,850 weight between 1 and max weight. 758 00:50:25,945 --> 00:50:28,490 And these were all integers-- 759 00:50:28,490 --> 00:50:29,110 ints. 760 00:50:29,110 --> 00:50:31,765 Suppose instead of that, I do this. 761 00:50:45,160 --> 00:50:50,780 So here after I choose my value between 1 and 10, I'm 762 00:50:50,780 --> 00:50:55,040 going to multiply it by some random number between 0 and 1. 763 00:50:58,340 --> 00:51:00,520 We've all seen random.random. 764 00:51:00,520 --> 00:51:03,770 So now how many possible different weights 765 00:51:03,770 --> 00:51:05,020 am I going to have? 766 00:51:08,550 --> 00:51:10,900 A huge number, right? 767 00:51:10,900 --> 00:51:17,680 The number of random numbers, real floats between 0 and 1 is 768 00:51:17,680 --> 00:51:20,540 close to infinite, right? 769 00:51:20,540 --> 00:51:24,030 And so in fact, now I'm going to see that with reasonable 770 00:51:24,030 --> 00:51:30,480 probability, every single item will have a different weight. 771 00:51:30,480 --> 00:51:34,530 And so, things adding up to the same could still happen, 772 00:51:34,530 --> 00:51:35,840 even if they're all different. 773 00:51:35,840 --> 00:51:38,840 But it's not very probable. 774 00:51:38,840 --> 00:51:41,750 So let's see what happens when I run it now. 775 00:51:51,170 --> 00:51:52,690 It's slowing down pretty quickly. 776 00:51:56,060 --> 00:51:58,420 Pretty much where the other one stopped right? 777 00:51:58,420 --> 00:52:02,240 Remember we got through 16 with Slowsolve 778 00:52:02,240 --> 00:52:04,450 but not through 32? 779 00:52:04,450 --> 00:52:09,860 Because effectively, I've now reduced the fast solve to the 780 00:52:09,860 --> 00:52:11,020 same as solve. 781 00:52:11,020 --> 00:52:14,100 It will never find anything in the memo, so it's doing 782 00:52:14,100 --> 00:52:16,980 exactly the same amount of work that solve did. 783 00:52:16,980 --> 00:52:18,430 And it will run very slowly. 784 00:52:21,790 --> 00:52:23,850 OK, life is hard. 785 00:52:23,850 --> 00:52:28,170 But this is what you'd expect, because deep down we know the 786 00:52:28,170 --> 00:52:33,640 0-1 knapsack problem is exponential, inherently 787 00:52:33,640 --> 00:52:35,180 exponential. 788 00:52:35,180 --> 00:52:38,450 And while I think dynamic programming is kind of 789 00:52:38,450 --> 00:52:43,180 miraculous how well it usually works, it's not miraculous in 790 00:52:43,180 --> 00:52:44,960 the liturgical sense, right? 791 00:52:44,960 --> 00:52:48,060 It's not actually performing a miracle in solving an 792 00:52:48,060 --> 00:52:51,140 exponential problem in linear time. 793 00:52:51,140 --> 00:52:53,920 It can in all things go bad. 794 00:52:53,920 --> 00:52:57,270 The exponential, and that's what it is here. 795 00:52:57,270 --> 00:52:59,100 This kind of algorithm is called pseudo-polynomial. 796 00:53:05,440 --> 00:53:11,380 It kind of runs in polynomial time, but not when things are 797 00:53:11,380 --> 00:53:12,630 really bad. 798 00:53:14,680 --> 00:53:18,960 And I won't go into the details of pseudo-polynomial, 799 00:53:18,960 --> 00:53:21,630 but if any of you are in Course 6, you'll hear about it 800 00:53:21,630 --> 00:53:25,040 in 6.006 and 6.046. 801 00:53:25,040 --> 00:53:27,920 OK, we can adjourn. 802 00:53:27,920 --> 00:53:29,170 I'll see you all on Thursday. 803 00:53:32,420 --> 00:53:33,710 And we're not going to wait for this to 804 00:53:33,710 --> 00:53:34,960 finish, because it won't.