Lecture 8 | Programming Abstractions (Stanford)
-
0:00 - 0:08
-
0:08 - 0:11
-
0:11 - 0:15This presentation is delivered by the Stanford Center for Professional Development.
-
0:15 - 0:24
-
0:24 - 0:26Good afternoon.
-
0:26 - 0:29You guys are talkative.
-
0:29 - 0:32So we talked just a little bit about the vocabulary of recursion at the
-
0:32 - 0:36end of Friday. It was very rushed for time, so I'm
-
0:36 - 0:40just gonna repeat some of those words to get us thinking about what the
-
0:40 - 0:42context for solving problems recursively looks like. And then we're gonna go along and
-
0:42 - 0:45do a lot of examples [cuts out].
-
0:45 - 0:48So the idea is that a recursive function is one that calls itself.
-
0:48 - 0:51That's really all it means
-
0:51 - 0:54at the most trivial level. It says that in the context of writing a
-
0:54 - 0:56function binky, it's gonna make a call
-
0:56 - 1:00or one or more calls to binky itself past
-
1:00 - 1:03an argument as part of solving or doing some task.
-
1:03 - 1:04The idea is that
-
1:04 - 1:08we're using that because the problem itself has some self-similarity where the
-
1:08 - 1:11answer I was seeking - so the idea of surveying the whole campus can actually be thought of as well if
-
1:11 - 1:14I could get somebody to survey this part of campus and this part of campus,
-
1:14 - 1:17somebody to survey all the freshmen, somebody to [cuts out]
-
1:17 - 1:18and whatnot,
-
1:18 - 1:21that those in a sense are - those surveys are just smaller instances of the same kind
-
1:21 - 1:24of problem I was originally trying to solve. And
-
1:24 - 1:26if I could recursively
-
1:26 - 1:30delegate those things out, and then they themselves may in turn delegate even
-
1:30 - 1:32further to smaller but same
-
1:32 - 1:36structured problems to where we could eventually get to something so simple - in
-
1:36 - 1:40that case of asking ten people for their input
-
1:40 - 1:44that don't require any further decomposition - we will have worked our way to this base
-
1:44 - 1:48case that then we can gather back up all the results and solve the whole thing.
-
1:48 - 1:51This is gonna feel very mysterious at first,
-
1:51 - 1:53and some of the examples I give you'll say I have
-
1:53 - 1:56really easy alternatives other than recursion, so it's not gonna seem worth
-
1:56 - 1:58the pain of trying to get your head around it.
-
1:58 - 2:03But eventually, we're gonna work our way up to problems where recursion really is the right solution,
-
2:03 - 2:06and there are no other alternatives that are obvious or simple
-
2:06 - 2:07to do.
-
2:07 - 2:07So the
-
2:07 - 2:11idea throughout this week is actually just a lot of practice. Telling you
-
2:11 - 2:13what the terms mean I think is not actually gonna help you understand it. I think what you
-
2:13 - 2:18need to see is examples. So I'm gonna be doing four or five examples today, four or five
-
2:18 - 2:20examples on Wednesday, and four or five examples on Friday
-
2:20 - 2:22that each kind of
-
2:22 - 2:25build on each other, kind of take the ideas
-
2:25 - 2:27and get a little more sophisticated.
-
2:27 - 2:28But
-
2:28 - 2:31by the end of the week, I'm hoping that you're gonna start to see these patterns, and realize
-
2:31 - 2:35that in some sense the recursive solutions tend to be more alike than different.
-
2:35 - 2:37Once you have your head around how to solve
-
2:37 - 2:41one type of problem, you may very well be able to take the exact same technique
-
2:41 - 2:43and solve several other problems that
-
2:43 - 2:46may sound different at first glance, but in the end, the recursive structure looks the
-
2:46 - 2:47same.
-
2:47 - 2:54So I'd say just hold the discomfort a little bit, and wait to see as
-
2:54 - 2:57we keep working which example may be the one that kind of sticks out for you to
-
2:57 - 2:59help you get it through.
-
2:59 - 3:02So we're gonna start with things that fit in the category of
-
3:02 - 3:03functional recursion.
-
3:03 - 3:05The functional in this case just says that
-
3:05 - 3:09you're writing functions that return some non-void thing, an integer, a string,
-
3:09 - 3:10some
-
3:10 - 3:12vector of results, or whatever that is,
-
3:12 - 3:14and that's all it means to be a functional recursion. It's a recursive
-
3:14 - 3:18function that has a result to it. And because of the recursive nature, it's gonna
-
3:18 - 3:22say that the outer problem's result, the answer to the larger problem is gonna be
-
3:22 - 3:26based on making calls, one or more calls to the function itself to get the answer
-
3:26 - 3:30to a smaller problem, and then adding them, multiplying them, comparing them to decide
-
3:30 - 3:33how to formulate the larger answer.
-
3:33 - 3:35All recursive
-
3:35 - 3:35code
-
3:35 - 3:38follows the same decomposition
-
3:38 - 3:42into two cases. Sometimes there's some subdivisions within there, but two general
-
3:42 - 3:43cases.
-
3:43 - 3:45The first thing - this base case.
-
3:45 - 3:48That's something that the recursion eventually has to stop.
-
3:48 - 3:52We keep saying take the task and break it down, make it a little smaller,
-
3:52 - 3:55but at some point we really have to stop doing that. We can't
-
3:55 - 3:57go on infinitely.
-
3:57 - 4:00There has to be some base case, the simplest possible version of the problem that
-
4:00 - 4:05you can directly solve. You don't need to make any further recursive calls. So the idea
-
4:05 - 4:10is that it kinda bottoms out there, and then allows the recursion to kind of
-
4:10 - 4:10unwind.
-
4:10 - 4:12The recursive cases,
-
4:12 - 4:15and there may be one or more of these, are the cases where it's not that simple, that the
-
4:15 - 4:20answer isn't directly solvable, but if you had the answer to a smaller, simpler
-
4:20 - 4:20version, you
-
4:20 - 4:24would be able to assemble that answer you're looking for
-
4:24 - 4:27using that information from the recursive call.
-
4:27 - 4:31So that's the kind of structure that they all look like. If I'm
-
4:31 - 4:34at the base case, then do the base case and return it.
-
4:34 - 4:37Otherwise, make some recursive calls, and
-
4:37 - 4:43use that to return a result from this iteration.
-
4:43 - 4:45So let's first look at something that
-
4:45 - 4:48- the first couple examples that I'm gonna show you actually are gonna be so
-
4:48 - 4:52easy that in some sense they're almost gonna be a little bit counterproductive because they're gonna
-
4:52 - 4:54teach you that recursion lets you do things that you already
-
4:54 - 4:57know how to do. And then I'm gonna work my way up to ones that actually
-
4:57 - 4:59get beyond that.
-
4:59 - 5:02But let's look at first the raise to power.
-
5:02 - 5:05C++ has no built-in exponentiation operator. There's nothing that raises
-
5:05 - 5:07a base to a particular exponent
-
5:07 - 5:08in the operator set.
-
5:08 - 5:12So if you want it, you need to write it, or you can use - there's a math library called
-
5:12 - 5:15pow for raise to power. We're gonna run our own version of it
-
5:15 - 5:17because it's gonna give us some practice thing about this.
-
5:17 - 5:20The first one I'm gonna show you is one that should feel very familiar and
-
5:20 - 5:24very intuitive, which is using an iterative formulation. If I'm
-
5:24 - 5:27trying to raise the base to the exponent, then that's really simply
-
5:27 - 5:30multiplying base by itself exponent times.
-
5:30 - 5:31So this one
-
5:31 - 5:32uses a for loop
-
5:32 - 5:37and does so. It starts the result at one, and for
-
5:37 - 5:40iterations through the number of exponents keeps
-
5:40 - 5:46multiplying to get there. So
-
5:46 - 5:48that one's fine, and it will perfectly work.
-
5:48 - 5:51I'm gonna show you this alternative way
-
5:51 - 5:54that starts you thinking about what it means to divide a problem up in a
-
5:54 - 5:56recursive strategy.
-
5:56 - 6:00Base to the exponent - I wanna raise five to the tenth power. If
-
6:00 - 6:05I had around some delegate, some
-
6:05 - 6:08clone of myself that I could dispatch to solve
-
6:08 - 6:11the slightly smaller problem of computing five to the ninth power,
-
6:11 - 6:15then all I would need to do is take that answer and multiply it by one more five, and I'd get
-
6:15 - 6:17five to the tenth.
-
6:17 - 6:19Okay.
-
6:19 - 6:22If I write code
-
6:22 - 6:25that's based on that,
-
6:25 - 6:28then I end up with something here - and I'm gonna
-
6:28 - 6:31let these two things go through to show us
-
6:31 - 6:32that
-
6:32 - 6:34to compute the answer to five
-
6:34 - 6:36to the tenth power,
-
6:36 - 6:38what I really need is the answer to five
-
6:38 - 6:42to the ninth power, which I do by making a recursive call to the same function I'm in
-
6:42 - 6:43the middle of writing.
-
6:43 - 6:45So this is raise that I'm
-
6:45 - 6:49defining, and in the body of it, it makes a call to raise. That's what is the
-
6:49 - 6:51mark of a recursive function here. I
-
6:51 - 6:55pass slightly different arguments. In this case, one smaller exponent
-
6:55 - 6:57which is getting a little bit closer
-
6:57 - 7:00to that simplest possible case
-
7:00 - 7:02that we will eventually terminate at where
-
7:02 - 7:07we can say I don't need to further dispatch any delegates and any clones out there
-
7:07 - 7:08to do the work, but if
-
7:08 - 7:11the exponent I'm raising it to is zero, by
-
7:11 - 7:16definition anything raised to the exponent of zero is one, I could just stop there.
-
7:16 - 7:19So when computing five to the tenth, we're gonna
-
7:19 - 7:21see some recursion at work. Let me
-
7:21 - 7:24take this code into the compiler
-
7:24 - 7:27
-
7:27 - 7:31so that we can see a little bit about how this actually works
-
7:31 - 7:32in terms of that.
-
7:32 - 7:35So that's exactly the code I have there, but
-
7:35 - 7:42I can say
-
7:42 - 7:43something that I
-
7:43 - 7:46know the answer to. How about that?
-
7:46 - 7:49
-
7:49 - 7:51First, we'll take a look at it
-
7:51 - 7:55doing its work, so five times five times five should be 125.
-
7:55 - 7:57So we can test a couple
-
7:57 - 7:58little
-
7:58 - 8:01values while we're at it. Two the sixth power, that should be
-
8:01 - 8:0764, just to see a couple of the cases, just to feel good about what's going on.
-
8:07 - 8:10And then raising say 23 to the zero power
-
8:10 - 8:11should be one
-
8:11 - 8:14as anything raised to the zero power should be.
-
8:14 - 8:16So a little bit of spot testing
-
8:16 - 8:18to feel good about what's going on.
-
8:18 - 8:22Now I'm gonna go back to this idea like two to the sixth.
-
8:22 - 8:24And I'm gonna set a breakpoint here.
-
8:24 - 8:25Get my breakpoint out.
-
8:25 - 8:36And I'm gonna run this guy in the debugger.
-
8:36 - 8:39Takes a little bit longer to get the debugger up and running,
-
8:39 - 8:44so I'll have to make a little padder up while we're going here.
-
8:44 - 8:47And then it tells me right now it's breaking on raise, and
-
8:47 - 8:53I can look around in the debugger. This is a - did not
-
8:53 - 8:56pick up my compilation? I think it did not. I
-
8:56 - 8:59must not have saved it right before I did it because it's actually got the base is 23 and the
-
8:59 - 9:00exponent is zero. It turns
-
9:00 - 9:06out I don't wanna see that case, so I'm gonna go back and
-
9:06 - 9:08try again. I
-
9:08 - 9:16wanna see it -
-
9:16 - 9:24no, I did not.
-
9:24 - 9:28And I'm just interested in knowing a little bit about the mechanics of what's gonna happen in a
-
9:28 - 9:29recursive situation.
-
9:29 - 9:33If I look at the first time that I hit my breakpoint,
-
9:33 - 9:35then I'll see that there's a little bit of the beginnings of the
-
9:35 - 9:39student main, some stuff behind it. There's a little bit of magic underneath your
-
9:39 - 9:43stack that you don't really need to know about, but starting from main it went into raise,
-
9:43 - 9:46and the arguments it has there is the base is two, the exponent is six.
-
9:46 - 9:50If I continue from here, then you'll notice the stack frame got one deeper.
-
9:50 - 9:53There's actually another indication of raise, and in fact, they're both active at the
-
9:53 - 9:54same time.
-
9:54 - 9:57The previous raise that was trying to compute two to the sixth
-
9:57 - 10:01is kind of in stasis back there waiting for the answer to come back from
-
10:01 - 10:05this version, which is looking to raise two to the fifth power. I continue again,
-
10:05 - 10:08I get two to the fourth. I keep doing this. I'm gonna see these guys kinda stack up,
-
10:08 - 10:12each one of those kind of waiting for the delegate or the clone
-
10:12 - 10:17to come back with that answer, so that then it can do its further work
-
10:17 - 10:19incorporating that result to compute the thing it needed to do.
-
10:19 - 10:22I get down here to raising
-
10:22 - 10:24two to the first power,
-
10:24 - 10:26and then finally I get to two to the zero,
-
10:26 - 10:29so now I've got these eight or so stacked frames,
-
10:29 - 10:30six up there.
-
10:30 - 10:32This one, if I step
-
10:32 - 10:34from here,
-
10:34 - 10:37it's gonna hit the base case of returning one,
-
10:37 - 10:39and then
-
10:39 - 10:43we will end up kind of working our way back out. So now, we are
-
10:43 - 10:46at the end of the two to the one case
-
10:46 - 10:49that's using the answer it got from the other one, multiplying it by two.
-
10:49 - 10:53Now I'm at the two to the two case, and so each of them's kind of unfolding in the stack is
-
10:53 - 10:55what's called unwinding. It's popping back off
-
10:55 - 10:57the stack frames that are there
-
10:57 - 11:01and kind of revisiting them, each passing up the information it got back,
-
11:01 - 11:04and eventually telling us that
-
11:04 - 11:08the answer was 64, so I will let that go.
-
11:08 - 11:15
-
11:15 - 11:17So the idea that
-
11:17 - 11:21all of those stack frames kind of exist at the same time - they're all being maintained
-
11:21 - 11:24independently, so the idea that the compiler isn't confused by the idea that raise is
-
11:24 - 11:26invoking raise which is invoking raise,
-
11:26 - 11:30that each of the raise stack frames is distinct from the other ones, so the
-
11:30 - 11:33indications are actually kept separate. So one had two to the sixth, the next one had two to
-
11:33 - 11:35the fifth, and so on.
-
11:35 - 11:36And then eventually
-
11:36 - 11:37we
-
11:37 - 11:40need to make some progress toward that base case, so that we can then
-
11:40 - 11:42stop that recursion
-
11:42 - 11:43and unwind.
-
11:43 - 11:47Let me actually show you something while I'm here, which is one thing that's
-
11:47 - 11:48a pretty common mistake
-
11:48 - 11:51to make early on in a recursion is to somehow fail to make progress toward
-
11:51 - 11:53that base case
-
11:53 - 11:57or to
-
11:57 - 11:58-
-
11:58 - 12:01not all cases make it to the base case. For example, if
-
12:01 - 12:03I did something where
-
12:03 - 12:06I forgot to
-
12:06 - 12:07subtract one [cuts out],
-
12:07 - 12:10and I said oh yeah, I need to [cuts out]. In this case, I'm passing it exactly the
-
12:10 - 12:13same arguments I got.
-
12:13 - 12:16If I do this and I run this guy,
-
12:16 - 12:19then what's gonna happen is it's gonna go two to the sixth, two to the sixth, two to the sixth,
-
12:19 - 12:20and I'm gonna
-
12:20 - 12:21let go of
-
12:21 - 12:23this breakpoint here because
-
12:23 - 12:30I don't really wanna watch it all happening.
-
12:30 - 12:31And there it goes.
-
12:31 - 12:35Loading 6,493 stack frames, zero percent completed, so
-
12:35 - 12:38that's gonna take a while as you
-
12:38 - 12:41can imaging. And usually, once you see this error message, it's your clue to say I
-
12:41 - 12:45can cancel, I know what happened. The only reason I should've
-
12:45 - 12:46gotten
-
12:46 - 12:506,500 stack frames loaded up is because I made a mistake that
-
12:50 - 12:53caused the stack to totally overflow.
-
12:53 - 12:57So the behavior in C++ or C is that when you have
-
12:57 - 13:01so many of those stack frames, eventually the space that's been allocated or set
-
13:01 - 13:03aside for the function stack will be exhausted.
-
13:03 - 13:05It will use all the space it has,
-
13:05 - 13:09and run up against a boundary, and typically report it in some way that
-
13:09 - 13:13suggests - sometimes you'll see stack overflow, stack out of memory error.
-
13:13 - 13:14
-
13:14 - 13:15In this case, it's showing you the
-
13:15 - 13:177,000 stack frames that
-
13:17 - 13:20are behind you, and then if you were to examine them you would see they all have exactly the
-
13:20 - 13:24same argument, so they weren't getting anywhere.
-
13:24 - 13:27I'm not gonna actually let it do that because I'm
-
13:27 - 13:28too impatient.
-
13:28 - 13:29Let
-
13:29 - 13:30me fix this code while I'm here.
-
13:30 - 13:33But other things like even this code that actually looks correct for
-
13:33 - 13:39some situations also has a subtle bug in it. Even if
-
13:39 - 13:40I fixed this, which is
-
13:40 - 13:44that, right now it assumed that the exponent is positive,
-
13:44 - 13:46that it's some number
-
13:46 - 13:47
-
13:47 - 13:49that I can subtract my way down to zero.
-
13:49 - 13:53If I actually miscalled raise, and I gave it a negative exponent, it would
-
13:53 - 13:57go into infinite recursion as well. If you started it
-
13:57 - 14:00at ten to the negative first power, it would go negative first, negative second, negative
-
14:00 - 14:02third.
-
14:02 - 14:056,500 stack frames later, we'd run out of space.
-
14:05 - 14:08In this case, since we're only intending to handle those positive powers, we
-
14:08 - 14:12could just put an error that was like if the exponent is less than zero then
-
14:12 - 14:14raise an error and don't even
-
14:14 - 14:21try to do anything with it. Okay. So let
-
14:21 - 14:25me show you a slightly different way of doing this that's also recursive,
-
14:25 - 14:28but that actually gets the answer a little bit more efficiently.
-
14:28 - 14:31This is a different way of dividing it up, but still using a
-
14:31 - 14:36recursive strategy which is that if I'm trying to compute five to the tenth power,
-
14:36 - 14:40but if I have the answer not of five to ninth power, but instead I have the
-
14:40 - 14:42answer of five to the fifth power,
-
14:42 - 14:44and then I multiply that by itself,
-
14:44 - 14:48I would get that five to the tenth power that I seek.
-
14:48 - 14:51And then there's a little bit of a case though of what if the power I was trying to
-
14:51 - 14:54get was odd, if I was trying to raise it to the eleventh power, I could compute the
-
14:54 - 14:58half power which get me to the fifth - multiplied by the fifth, but then I need
-
14:58 - 14:59one more
-
14:59 - 15:04base multiplied in there to make up for that half. Okay.
-
15:04 - 15:05And so I can write
-
15:05 - 15:08another recursive formulation here. Same
-
15:08 - 15:11sort of base case about
-
15:11 - 15:14detecting when we've gotten down, but then in this case the recursive call we make is
-
15:14 - 15:17to base of the exponent divided by two,
-
15:17 - 15:19and then we use it with an
-
15:19 - 15:23if else whether the exponent was even or odd, it decided whether to just square
-
15:23 - 15:25that number or whether to square it and
-
15:25 - 15:28tack in another power of the base while we're at it.
-
15:28 - 15:31So this one is gonna be much more quick about getting down to it,
-
15:31 - 15:34whereas the one we saw was gonna put one stack frame down for every
-
15:34 - 15:35
-
15:35 - 15:39successive exponent power. So if you wanted to raise something to the 10th, or 20th,
-
15:39 - 15:41or 30th power, then you were
-
15:41 - 15:43giving yourself 10, 20, 30 stack frames.
-
15:43 - 15:46Something like 30 stack frames is not really something to be worried
-
15:46 - 15:48about, but if you were really trying to make this work on much larger
-
15:48 - 15:49numbers,
-
15:49 - 15:52which would require some other work because exponent
-
15:52 - 15:55is a very rapidly growing operator and would overflow your integers quickly,
-
15:55 - 15:57but this way
-
15:57 - 16:00very quickly divides in half. So it goes from a power of 100, to a power of
-
16:00 - 16:0550, to 25, to 12, to 6, to 3, to 2, to 1, so
-
16:05 - 16:08that dividing in half is a much quicker way to work our way down to that base case
-
16:08 - 16:12and get our answer back, and we're doing a lot fewer calculations
-
16:12 - 16:14than all those multiplies,
-
16:14 - 16:16one per
-
16:16 - 16:19base. So just a little
-
16:19 - 16:21diversion.
-
16:21 - 16:22So let me tell you something,
-
16:22 - 16:23just a little bit about
-
16:23 - 16:27kind of style as it applies to recursion.
-
16:27 - 16:28Recursion is
-
16:28 - 16:34really best when you can express it in the most kind of direct, clear, and
-
16:34 - 16:36simple code.
-
16:36 - 16:38It's hard enough to get your head around a recursive formulation
-
16:38 - 16:42without complicating it by having a bunch of extraneous parts where
-
16:42 - 16:43you're
-
16:43 - 16:47doing more work than necessary, or redundantly handling certain things. And
-
16:47 - 16:49one thing that's actually very easy to fall in the trap of
-
16:49 - 16:52is thinking about there's lots of other base cases you might be able to
-
16:52 - 16:56easily handle, so why not just go ahead and call out for them,
-
16:56 - 16:57test for
-
16:57 - 16:59- you're at the base case. You're close to the base case.
-
16:59 - 17:02Checking before you might make a recursive call, if you're gonna hit the base case when you
-
17:02 - 17:08make that call, then why make the call. I'll just anticipate and get the answer it
-
17:08 - 17:09would've returned anyway.
-
17:09 - 17:13It can lead to code that looks a little bit like this before you're done. If the
-
17:13 - 17:16exponent's zero, that's one. If the exponent's one, then I can just
-
17:16 - 17:19return the base. If it's two, then I can just multiply the base by itself. If it's
-
17:19 - 17:20three, I can start doing this.
-
17:20 - 17:22Certainly, you
-
17:22 - 17:25can follow this to the point of absurdity, and even for some of the simple
-
17:25 - 17:28cases, it might seem like you're saving yourself a little bit of extra time. You're like
-
17:28 - 17:31why go back around and let it make another recursive call. I could stop it right
-
17:31 - 17:33here. It's easy to know that answer.
-
17:33 - 17:36But as you do this, it complicates the code. It's a little harder to read. It's
-
17:36 - 17:38a little harder to
-
17:38 - 17:39debug.
-
17:39 - 17:43Really, the expense of making that one extra call, two extra calls
-
17:43 - 17:46is not the thing to be worried about.
-
17:46 - 17:50What we really want is the cleanest code that expresses what we do, has the
-
17:50 - 17:53simplest cases to test, and to examine, and to follow,
-
17:53 - 17:56and not muck it up with things that in
-
17:56 - 17:57effect
-
17:57 - 18:01don't change the computational power of this but just introduce the opportunity for error.
-
18:01 - 18:02Instead of a
-
18:02 - 18:05multiply here, I accidentally put a plus.
-
18:05 - 18:07It might be easy to overlook it
-
18:07 - 18:08and not realize what I've done,
-
18:08 - 18:10and then
-
18:10 - 18:14end up computing the wrong answer when it gets to the base case of two. In fact, if
-
18:14 - 18:15you do it this
-
18:15 - 18:17way, most things would stop at three,
-
18:17 - 18:20but these would suddenly become kind of their own special cases that were only
-
18:20 - 18:25hit if you directly called them with the two. The recursive cases will all stop earlier. It
-
18:25 - 18:28just complicates your testing pass now because not everything is going through
-
18:28 - 18:31the same code.
-
18:31 - 18:34So we call this arm's length recursion. I put a big X on that
-
18:34 - 18:35looking ahead,
-
18:35 - 18:36testing before you get there.
-
18:36 - 18:41Just let the code fall through.
-
18:41 - 18:43So
-
18:43 - 18:45Dan, he's not here today,
-
18:45 - 18:48but he talked to me at the end of Friday's class, and it made
-
18:48 - 18:49me
-
18:49 - 18:53wanna actually just give you a little bit of
-
18:53 - 18:54insight into
-
18:54 - 18:57recursion as it relates to
-
18:57 - 18:58
-
18:58 - 19:02efficiency. Recursion by itself, just the idea of applying a recursive technique to solving a
-
19:02 - 19:06problem, does not give you any guarantee that it's gonna be the best solution, the
-
19:06 - 19:09most efficient solution, or the fact that it's gonna give you a very inefficient solution.
-
19:09 - 19:11Sometimes people have kind of
-
19:11 - 19:11a
-
19:11 - 19:15bad rap on recursion. It's like recursion will definitely be inefficient.
-
19:15 - 19:17That actually is not guaranteed.
-
19:17 - 19:21Recursion often requires exactly the same resources as the iterative approach.
-
19:21 - 19:24It takes the same amount of effort. Surveying the campus - if you're gonna
-
19:24 - 19:27survey the 10,000 people on campus and get everybody's information back,
-
19:27 - 19:30whether you're doing it divide and conquer, or whether you're sitting out there in White
-
19:30 - 19:30Plaza
-
19:30 - 19:34asking each person one by one, in the end 10,000 people got survey.
-
19:34 - 19:36The recursion is not
-
19:36 - 19:39part of what made that longer or shorter. It might actually depending on how you
-
19:39 - 19:42have resources work out better. Like if you could get a bunch of people in parallel
-
19:42 - 19:46surveying, you might end up completing the whole thing in less time, but it required
-
19:46 - 19:49more people, and more clipboards, and more paper
-
19:49 - 19:52while the process is ongoing than me standing there with one piece of paper and a
-
19:52 - 19:57clipboard. But then again, it took lots of my time to do it.
-
19:57 - 20:00So in many situations, it's really no better or no worse
-
20:00 - 20:02than the alternative. It makes a little bit of some tradeoffs of where the time is
-
20:02 - 20:03spent.
-
20:03 - 20:06There are situations though where recursion can actually make something that was
-
20:06 - 20:09efficient inefficient. There are situations where it can take something that was inefficient and
-
20:09 - 20:11make it efficient. So
-
20:11 - 20:15it's more of a case-by-case basis to decide whether recursion is the
-
20:15 - 20:18right tool if efficiency is one of your primary concerns.
-
20:18 - 20:21I will say that for problems with simple iterative solutions
-
20:21 - 20:24that operate relatively efficiently, iteration
-
20:24 - 20:26is probably the best way to solve it.
-
20:26 - 20:27So like
-
20:27 - 20:31the raise to power, yeah you could certainly do that
-
20:31 - 20:32iteratively. There's not
-
20:32 - 20:35some huge advantage that recursion is offering. I'm using them here because they're
-
20:35 - 20:37simple enough to get our head around.
-
20:37 - 20:39We're gonna work our way toward problems where
-
20:39 - 20:42we're gonna find things that require recursion, which
-
20:42 - 20:45is kind of one of the third points I put in. Why do we learn recursion? What's recursion gonna be good
-
20:45 - 20:48for? First off, recursion is awesome.
-
20:48 - 20:52There are some problems that you just can't solve using anything but recursion,
-
20:52 - 20:55so that the alternatives like I'll just write it iteratively won't work.
-
20:55 - 20:59You'll try, but you'll fail. They inherently have a recursive
-
20:59 - 20:59structure to them
-
20:59 - 21:02where recursion is the right tool for the job.
-
21:02 - 21:06Often, it produces the most beautiful, direct, and clear elegant code.
-
21:06 - 21:08The next assignment that will go out
-
21:08 - 21:11has these problems that you do in recursion, and they're each about ten lines
-
21:11 - 21:13long. Some of them are like five lines long.
-
21:13 - 21:18They do incredible things in five lines because of the descriptive power of
-
21:18 - 21:19describing it as
-
21:19 - 21:22here's a base case, and here's a recursive case, and everything else just follows
-
21:22 - 21:23from
-
21:23 - 21:26applying this, and reducing the problem step by step.
-
21:26 - 21:28So you will see things where
-
21:28 - 21:31the recursive code is just beautiful, clean, elegant, easy to understand, easy to follow,
-
21:31 - 21:32easy to test,
-
21:32 - 21:34solves the recursive problem. And in
-
21:34 - 21:37those cases, it really is a much better answer than trying to hack up some
-
21:37 - 21:39other iterative form
-
21:39 - 21:41that may in the end be
-
21:41 - 21:44no more efficient. It may be even less efficient. So don't let efficiency be
-
21:44 - 21:46kind of a big
-
21:46 - 21:52fear of what recursion is for you. So I'm gonna do more examples.
-
21:52 - 21:54
-
21:54 - 21:56I've got three more examples,
-
21:56 - 21:58or four I think today, so
-
21:58 - 22:01I will just keep
-
22:01 - 22:03showing you different things, and hopefully
-
22:03 - 22:05the patterns will start to kind of
-
22:05 - 22:07come out of the mist for you.
-
22:07 - 22:10A palindrome string is one that reads the same forwards and backwards
-
22:10 - 22:14when reversed. So was it a car or a cat I saw, if you read that
-
22:14 - 22:16backwards, it turns out
-
22:16 - 22:17it
-
22:17 - 22:21says the same thing. Go hang a salami, I'm a lasagna
-
22:21 - 22:22hog. Also
-
22:22 - 22:26handy when you need to have a bar bet over what the longest palindrome is
-
22:26 - 22:28to have these things around.
-
22:28 - 22:31There are certainly ways to do this iteratively. If you were given a string and you were interested to
-
22:31 - 22:33know is it a palindrome,
-
22:33 - 22:36you could kind of do this marching - you're looking outside and kind of marching your way
-
22:36 - 22:38into the middle.
-
22:38 - 22:42But we're gonna go ahead and let recursion kinda help us
-
22:42 - 22:45deal with the subproblem of this,
-
22:45 - 22:47and imagine that at the simplest possible form - you could say that
-
22:47 - 22:49a palindrome
-
22:49 - 22:51consists of
-
22:51 - 22:53an interior palindrome,
-
22:53 - 22:56and then the same letter tacked on to the front and the back.
-
22:56 - 22:58So if you look at was it a car or a cat I saw,
-
22:58 - 23:01there are two Ws there. It starts and it ends with a W,
-
23:01 - 23:04so all palindromes must start and end with the same letter. Okay.
-
23:04 - 23:05Let's check that,
-
23:05 - 23:11and if that matches, then extract that middle and see if it's a palindrome.
-
23:11 - 23:13So it feels like I didn't really do anything. It's like
-
23:13 - 23:17all I did was match two letters, and then I said by the way
-
23:17 - 23:19delegate this problem
-
23:19 - 23:23back to myself, making a call to a function I haven't even finished writing
-
23:23 - 23:27to examine the rest of the letters.
-
23:27 - 23:29And then I need a base case.
-
23:29 - 23:31I've got a recursive case, right?
-
23:31 - 23:34Take off the outer two letters. Make sure they match.
-
23:34 - 23:37Recur on the inside.
-
23:37 - 23:42What is the simplest possible palindrome? One letter? One
-
23:42 - 23:44letter. One letter makes a good palindrome. One letter is by
-
23:44 - 23:47definition first and last letter are the same letter, so it matches. That's
-
23:47 - 23:53a great base case. Is it the only base case? [Inaudible]. Two
-
23:53 - 23:56letters is also kind of important, but there's actually an even
-
23:56 - 23:58simpler form, right?
-
23:58 - 24:02It's the empty string. So both the empty string and the single character string are by
-
24:02 - 24:05definition the world's simplest palindromes. They meet the
-
24:05 - 24:08requirements that they read the same forward and backwards. Empty string forwards
-
24:08 - 24:10and backwards is trivially the
-
24:10 - 24:14same, so that makes it even easier than doing the two-letter case.
-
24:14 - 24:17So if I write this code to look like that where
-
24:17 - 24:19if the length of the strength is
-
24:19 - 24:24one or fewer, so that handles both the zero and the one case, then I return true.
-
24:24 - 24:25Those are trivial
-
24:25 - 24:26
-
24:26 - 24:28palindromes of the easiest
-
24:28 - 24:30immediate detection.
-
24:30 - 24:31Otherwise,
-
24:31 - 24:33I've got a return here that says
-
24:33 - 24:35if the first character
-
24:35 - 24:38is the same as the last character,
-
24:38 - 24:39and the
-
24:39 - 24:42middle - so the substring starting at Position 1
-
24:42 - 24:47that goes for length minus two characters that picks up the interior
-
24:47 - 24:50discarding the first and last characters - if that's also a palindrome, then we've got a
-
24:50 - 24:51palindrome.
-
24:51 - 24:54So given the short circuiting nature of the and, if
-
24:54 - 24:56it looks at the outer two characters and they don't match, it immediately just
-
24:56 - 24:58stops right there and says false.
-
24:58 - 25:01If they do match, then it goes on looking
-
25:01 - 25:03at the next interior pair
-
25:03 - 25:07which will stack up a recursive call looking at its two things, and eventually we
-
25:07 - 25:10will either catch a pair that doesn't match,
-
25:10 - 25:14and then this false will immediately return its way out, or it will keep
-
25:14 - 25:17going all the way down to that base case, hit a true,
-
25:17 - 25:21and know that we do have a full palindrome there.
-
25:21 - 25:24So you could certainly write this with a for loop. Actually, writing
-
25:24 - 25:27it with a for loop is almost a little bit trickier because you
-
25:27 - 25:30have to keep track of which part of the string are you on, and what happens when you
-
25:30 - 25:34get to the middle and things like that. In some sense, the recursive
-
25:34 - 25:35form really kinda
-
25:35 - 25:38sidesteps that by really thinking about it in a more holistic way
-
25:38 - 25:41of like the outer letters plus an inner palindrome
-
25:41 - 25:43gives me the answer I'm looking for.
-
25:43 - 25:46So this idea of
-
25:46 - 25:49taking a function you're in the middle of writing and making a call to it as
-
25:49 - 25:50though it worked
-
25:50 - 25:53is something that - they require this idea of the leap of faith.
-
25:53 - 25:55You haven't even finished describing
-
25:55 - 25:58how is palindrome operates,
-
25:58 - 26:01and there you are making a call to it depending on it working in order to get
-
26:01 - 26:02your
-
26:02 - 26:04function working. It's a very kind of
-
26:04 - 26:06wacky thing to get your head around.
-
26:06 - 26:08It feels a little bit
-
26:08 - 26:09mystical at first.
-
26:09 - 26:11
-
26:11 - 26:13That feeling you feel a little bit
-
26:13 - 26:16discombobulated about this is probably pretty normal,
-
26:16 - 26:17but we're gonna
-
26:17 - 26:18keep seeing examples,
-
26:18 - 26:21and hope that
-
26:21 - 26:23it starts to feel a little less unsettling.
-
26:23 - 26:26Anybody wanna ask a question about this so far? Yeah?
-
26:26 - 26:28So I guess create your
-
26:28 - 26:30base case first, then test it? [Inaudible]. That's a great question.
-
26:30 - 26:35So I would say typically that's a great strategy. Get a base
-
26:35 - 26:38case and test against the base case, so the one character and the two character
-
26:38 - 26:39strings.
-
26:39 - 26:42And then imagine one layer out, things that will make one recursive
-
26:42 - 26:46call only. So in this case for the palindrome, it's like what's a two-character
-
26:46 - 26:47string?
-
26:47 - 26:50One has AB. One has AA. So one that is a palindrome, one that isn't,
-
26:50 - 26:52and watch it go through.
-
26:52 - 26:55Then from there you have to almost take that leap and say
-
26:55 - 26:58it worked for the base case. It worked for one out. And then you have to start
-
26:58 - 27:02imagining if it worked for a string of length N, it'll work for one of N plus one,
-
27:02 - 27:03and
-
27:03 - 27:07that in some sense testing it exhaustively across all strings is
-
27:07 - 27:09of course impossible, so you have to kind of
-
27:09 - 27:12move to a sort of larger case where you can't just sit there and trace the whole
-
27:12 - 27:15thing. You'll have to in some sense take a faith thing that says
-
27:15 - 27:18if it could have computed whether the interior's a palindrome, then adding two
-
27:18 - 27:21characters on the outside
-
27:21 - 27:21
-
27:21 - 27:24and massaging that with that answer should produce the right thing. So some bigger
-
27:24 - 27:26tests
-
27:26 - 27:28that
-
27:28 - 27:30verify that the recursive case
-
27:30 - 27:32when exercised does the right thing,
-
27:32 - 27:34then the pair together - All your code is going through
-
27:34 - 27:38these same lines. The outer case going down to the recursive case, down
-
27:38 - 27:39to that base case, and that's
-
27:39 - 27:43one of the beauty of writing it recursively is that in some sense
-
27:43 - 27:46once this piece of code works for some simple cases, the idea that setting it to
-
27:46 - 27:48larger cases
-
27:48 - 27:51is almost - I don't wanna
-
27:51 - 27:54say guaranteed. That maybe makes it sound too easy, but in fact, if it
-
27:54 - 27:55works for
-
27:55 - 27:58cases of N and then N plus one, then it'll work for 100, and 101, and
-
27:58 - 28:026,000, and 6,001, and all the way through all the numbers by
-
28:02 - 28:06induction. Question?
-
28:06 - 28:10You have to remove all the [inaudible], all the spaces? Yeah. So definitely like the was it a car to match I should definitely
-
28:10 - 28:13be taking my spaces out of there to make it right. You
-
28:13 - 28:21are totally correct on that.
-
28:21 - 28:22So let me show you
-
28:22 - 28:25one where recursion is definitely
-
28:25 - 28:27gonna buy us some real efficiency
-
28:27 - 28:30and some real clarity in solving a
-
28:30 - 28:32search problem. I've got a
-
28:32 - 28:35vector. Let's say it's a vector of strings. It's a vector
-
28:35 - 28:37of numbers. A vector of anything, it doesn't really matter.
-
28:37 - 28:40And I wanna see if I can find a particular entry in it. So unlike the
-
28:40 - 28:44set which can do a fast contains for you, in vector
-
28:44 - 28:46if I haven't done anything special with it,
-
28:46 - 28:49then there's no guarantee about where to find something. So if I wanna say
-
28:49 - 28:51
-
28:51 - 28:54did somebody score 75 on the exam, then I'm gonna have
-
28:54 - 28:56to just walk through the vector starting at Slot
-
28:56 - 28:590, walk my way to the end, and
-
28:59 - 29:02compare to see if I find a 75. If I get to the end and I haven't found one, then
-
29:02 - 29:04I can say no.
-
29:04 - 29:05So that's what's called linear search. Linear
-
29:05 - 29:10kind of implies this left to right sequential processing.
-
29:10 - 29:11And linear search
-
29:11 - 29:15has the property that as the size of the input grows, the amount of time
-
29:15 - 29:18taken to search it grows in proportion.
-
29:18 - 29:20So if you have a 1000 number array,
-
29:20 - 29:23and you doubled its size, you would expect that doing a linear search on it should take
-
29:23 - 29:30twice as long for an array that's twice as large.
-
29:30 - 29:34The strategy we're gonna look at today is binary search
-
29:34 - 29:37which is gonna try to avoid this looking in every one of those boxes to
-
29:37 - 29:38find something.
-
29:38 - 29:42It's gonna take a divide and conquer strategy, and it's gonna require a sorted
-
29:42 - 29:42vector.
-
29:42 - 29:45So in order to do an efficient lookup,
-
29:45 - 29:48it helps if we've done some pre-rearrangement
-
29:48 - 29:49
-
29:49 - 29:51of the data. In this case, putting it into sorted order
-
29:51 - 29:55is gonna make it much easier for us to be able to find something in it because
-
29:55 - 29:55we have
-
29:55 - 29:58better information about where to look,
-
29:58 - 29:59so much faster.
-
29:59 - 30:03So we'll see that surely there was some cost to that,
-
30:03 - 30:05but typically binary search is gonna be used when you have an array where you
-
30:05 - 30:07don't do a lot of changes to it so
-
30:07 - 30:11that putting it in a sorted order can be done once, and then from that point forward you can
-
30:11 - 30:15search it many times, getting the benefit of the work you did to put it in
-
30:15 - 30:16sorted order.
-
30:16 - 30:19If you plan to sort it just to search it, then in some sense all the time you spent
-
30:19 - 30:20sorting it
-
30:20 - 30:25would kind of count against you and unlikely to come out ahead.
-
30:25 - 30:28So the insight we're gonna use is
-
30:28 - 30:32that if we have this in sorted order, and
-
30:32 - 30:34we're trying to search the whole thing - we're looking for let's say the No. 75
-
30:34 - 30:36-
-
30:36 - 30:38is that if we just look at the middlemost element,
-
30:38 - 30:40so we have this idea that we're looking at the whole vector right now from the
-
30:40 - 30:42start to the end,
-
30:42 - 30:45and I look at the middle element, and I say it's a 54. I can say
-
30:45 - 30:49if 75 is in this vector because it's in sorted order,
-
30:49 - 30:51it can't be
-
30:51 - 30:52anywhere over here.
-
30:52 - 30:55If 54's right there, everything to the left of 54
-
30:55 - 30:57must be less than that,
-
30:57 - 30:59and 75 wouldn't be over there.
-
30:59 - 31:00So that means I can
-
31:00 - 31:03just discard that half of the
-
31:03 - 31:06vector from further consideration.
-
31:06 - 31:07So now I have this half
-
31:07 - 31:10to continue looking at which is the things that
-
31:10 - 31:13are the right of 54 all the way to the end.
-
31:13 - 31:17I use the same strategy again. I say if I'm searching a vector - so
-
31:17 - 31:19last time I was searching a vector that had 25 elements. Now I've got one that's got
-
31:19 - 31:21just 12.
-
31:21 - 31:24Again, I use the same strategy. Look at the one in the middle.
-
31:24 - 31:26I say oh, it's an 80,
-
31:26 - 31:29and then I say well the number I'm looking for is 75.
-
31:29 - 31:30It can't be
-
31:30 - 31:35to the right of the 80. It must be to the left of it.
-
31:35 - 31:36And then that lets me
-
31:36 - 31:40get rid of another quarter of the vector. If I keep doing this, I get rid of
-
31:40 - 31:43half, and then a quarter, and then an eighth, and
-
31:43 - 31:47then a 16th. Very quickly, I will narrow in on the position where if 75 is in
-
31:47 - 31:53this vector, it has to be. Or I'll be able to conclude it wasn't there at all. Then I kind of
-
31:53 - 31:55work on my in, and
-
31:55 - 31:59I found a 74 and a 76 over there, then I'm done.
-
31:59 - 32:02That base case comes when I have
-
32:02 - 32:04such a small little vector there
-
32:04 - 32:05where
-
32:05 - 32:09my bounds have crossed in such a way that I can say I never found what I
-
32:09 - 32:16was looking for.
-
32:16 - 32:19So let's walk through
-
32:19 - 32:21this
-
32:21 - 32:22
-
32:22 - 32:25bit of code that kind of puts into C++ the thing I just described here.
-
32:25 - 32:29I've got a vector. In this case, I'm using a vector that's containing strings.
-
32:29 - 32:32It could be ints. It could be anything. It doesn't really matter.
-
32:32 - 32:36I've got a start and a stop, which I identify the sub-portion of the vector
-
32:36 - 32:40that we're interested in. So the start is the first index to consider. The stop is the
-
32:40 - 32:41last index to consider.
-
32:41 - 32:46So the very first call to this will have start set to zero and stop set to the vector's
-
32:46 - 32:48size minus one. I
-
32:48 - 32:51compute the midpoint index
-
32:51 - 32:54which is just the sum of the start and stop divided by two,
-
32:54 - 32:56and then I compare
-
32:56 - 32:58the key that I'm looking for to
-
32:58 - 33:02the value at that index. I'm looking right in the middle. If it happens to match,
-
33:02 - 33:06then I return that. The goal of binary search in this case is to return the index of a
-
33:06 - 33:08matching element within the vector,
-
33:08 - 33:11or to return this not found negative one constant
-
33:11 - 33:14if it was unable to find any match anywhere.
-
33:14 - 33:16So when we do find it
-
33:16 - 33:20at whatever the level the recursion is, we can just immediately return that. We're done.
-
33:20 - 33:21We found it. It's good.
-
33:21 - 33:24Otherwise, we're gonna make this recursive call
-
33:24 - 33:28that looks at either the left half or the right half based on if key is
-
33:28 - 33:28less than
-
33:28 - 33:30the value we found at the midpoint,
-
33:30 - 33:32then the place we're searching is -
-
33:32 - 33:34still has the same start position,
-
33:34 - 33:36but is now capped
-
33:36 - 33:38by the
-
33:38 - 33:40element exactly to the left of the midpoint,
-
33:40 - 33:43and then the right one,
-
33:43 - 33:45the inversion of that,
-
33:45 - 33:47one to the right of the midpoint
-
33:47 - 33:49and the stop unchanged.
-
33:49 - 33:53So taking off half of the elements under consideration at each stage,
-
33:53 - 33:54eventually
-
33:54 - 33:58I will get down to the simplest possible case. And the simplest possible case isn't that I have a one-element
-
33:58 - 34:02vector and I found it or not. The really simple case is actually that I have zero
-
34:02 - 34:03elements in my vector
-
34:03 - 34:04that I just kept
-
34:04 - 34:06moving in the
-
34:06 - 34:09upper and lower bound point until they crossed,
-
34:09 - 34:12which meant that I ran out of elements to check.
-
34:12 - 34:15And that happens when the start index is greater than the stop index. So the start
-
34:15 - 34:19and the stop if they're equal to each other mean that you have a one element vector left
-
34:19 - 34:20to search,
-
34:20 - 34:22but if you - For
-
34:22 - 34:24example, if you got to that case where you have that one element vector left to search, you'll look
-
34:24 - 34:28at that one, and if it matches, you'll be done. Otherwise, you'll end up
-
34:28 - 34:30either decrementing the stop
-
34:30 - 34:33to move past the start, or incrementing the start to move past the
-
34:33 - 34:34stop.
-
34:34 - 34:36And then that next iteration will hit this base case that said
-
34:36 - 34:40I looked at the element in a one-element vector. It didn't work
-
34:40 - 34:40out.
-
34:40 - 34:43I can tell you for sure it's not found.
-
34:43 - 34:45If it had been here, I would've seen it.
-
34:45 - 34:47And this is looking at just one element
-
34:47 - 34:49each
-
34:49 - 34:50recursive call,
-
34:50 - 34:55and the recursive calls in this case stack up to a depth
-
34:55 - 34:57based on the logarithm of the size
-
34:57 - 34:59to the power of two,
-
34:59 - 35:02so that if you have 1000 elements,
-
35:02 - 35:06you look at one, and now you have a 500-element collection to look
-
35:06 - 35:09at again. You look at one, you have
-
35:09 - 35:12a 250 element, 125, 60, 30,
-
35:12 - 35:1215.
-
35:12 - 35:14So at each stage
-
35:14 - 35:15half of them
-
35:15 - 35:19remain for the further call, and the number of times you can do that
-
35:19 - 35:22for 1000 is the number of times you can divide 1000 by two, which is
-
35:22 - 35:24the log based two of that,
-
35:24 - 35:26which is roughly ten.
-
35:26 - 35:29So if you were looking at 1000 number array, if it's in sorted order,
-
35:29 - 35:32it takes you ten comparisons
-
35:32 - 35:34to conclusively determine
-
35:34 - 35:38where an element is if it's in there, or that it doesn't exist in the array at
-
35:38 - 35:39all.
-
35:39 - 35:41If you take that 1000 element array,
-
35:41 - 35:43and you make it twice as big,
-
35:43 - 35:46so now I have a 2000
-
35:46 - 35:50number array, how much longer does it take? One more step.
-
35:50 - 35:51One more step.
-
35:51 - 35:55Just one, right? You look at one, and you have a 1000 number array, so however long it took
-
35:55 - 35:56you to do that 1000 number array,
-
35:56 - 36:00it takes one additional comparison, kinda one stack frame on top of that
-
36:00 - 36:01to get its way down.
-
36:01 - 36:04So this means actually this is super efficient.
-
36:04 - 36:06That you can search so for example
-
36:06 - 36:09a million is roughly two the 20th power.
-
36:09 - 36:12So you have a million entry
-
36:12 - 36:16collection to search, it will take you 20 comparisons to say for sure
-
36:16 - 36:17it's here or not,
-
36:17 - 36:18and here's where I found it,
-
36:18 - 36:20just 20.
-
36:20 - 36:22You go up to two million, it takes
-
36:22 - 36:2421. The
-
36:24 - 36:27very slow growing function, that logarithm function, so that tells you
-
36:27 - 36:28
-
36:28 - 36:31that this is gonna be a very efficient way of searching a sorted array. A
-
36:31 - 36:34category
-
36:34 - 36:37thing called the divide and conquer that
-
36:37 - 36:39take a problem,
-
36:39 - 36:42divide it typically in half, but sometimes in thirds or some other
-
36:42 - 36:45way, and then kind of - in this case
-
36:45 - 36:49it's particularly handy that we can throw away some part of the problem, so we
-
36:49 - 36:56divide and focus on just one part to solve the problem.
-
36:56 - 36:58All right. So this is the first one
-
36:58 - 37:02that's gonna start to
-
37:02 - 37:04really inspire you for how recursion can help you solve problems
-
37:04 - 37:05that you might
-
37:05 - 37:08have no idea how to approach any other way
-
37:08 - 37:10than using a recursive formulation.
-
37:10 - 37:13So this is an exercise that comes out of the reader in Chapter 4,
-
37:13 - 37:16and the
-
37:16 - 37:18context of it is you have N things,
-
37:18 - 37:22so maybe it's N people in a dorm, 60 people in a dorm,
-
37:22 - 37:26and you would like to choose K of them. Let's K a real number, four
-
37:26 - 37:29- four people to go together to Flicks.
-
37:29 - 37:32So of your 60 dorm mates, how many different ways could you
-
37:32 - 37:33pick a subset
-
37:33 - 37:35of size four
-
37:35 - 37:38that doesn't repeat any of the others? So you can pick the
-
37:38 - 37:41two people from the first floor, one person from the middle floor, one person
-
37:41 - 37:44from the top floor, but then you can kind of shuffle it up. What if you took all the people from the
-
37:44 - 37:45first floor, or
-
37:45 - 37:48these people from that room, and whatnot? You can imagine there's a lot of kind of
-
37:48 - 37:50machinations
-
37:50 - 37:53that could be present here,
-
37:53 - 37:57and counting them, it's not quite obvious
-
37:57 - 37:59unless you kinda go back to start working on your real math
-
37:59 - 38:01skills
-
38:01 - 38:05how it is that you can write a formula for this.
-
38:05 - 38:07So what I'm gonna give you is
-
38:07 - 38:11a recursive way of thinking about this problem.
-
38:11 - 38:12So I drew
-
38:12 - 38:15a set of the things we're looking at?
-
38:15 - 38:18So there are N things that we're trying to choose K out
-
38:18 - 38:21of. So right now, I've got
-
38:21 - 38:2312 or so
-
38:23 - 38:24people, or
-
38:24 - 38:26items, whatever it is.
-
38:26 - 38:29What I'm gonna do is I'm gonna imagine just designating one
-
38:29 - 38:30totally at random.
-
38:30 - 38:33So pick Bob.
-
38:33 - 38:35Bob is one of the people in the dorm. I'm gonna kind of separate him from
-
38:35 - 38:39everybody else mentally in my mind, and draw this line, and kinda mark him with a red flag
-
38:39 - 38:41that says that's Bob.
-
38:41 - 38:45So Bob might go to Flicks or might not go to Flicks. Some of the subsets for
-
38:45 - 38:52going to Flicks will include Bob, and some will not.
-
38:52 - 38:53Okay.
-
38:53 - 38:57So what I'd like to think about is how many different subsets can I make that will
-
38:57 - 38:58include Bob,
-
38:58 - 39:01and how many different subsets can I make that don't include Bob. And
-
39:01 - 39:05if I added those together, then that should be the total number of subsets I can make
-
39:05 - 39:08from this collection.
-
39:08 - 39:11Okay, so the subsets that include Bob -
-
39:11 - 39:15so once I've committed to Bob being in the set, and let's say I'm trying to pick four
-
39:15 - 39:16members out of here,
-
39:16 - 39:18then I have Bob
-
39:18 - 39:22and I have to figure out how many ways can I pick three people to accompany Bob to
-
39:22 - 39:24the Flicks.
-
39:24 - 39:28So I'm picking from a slightly smaller population. The population went down by one,
-
39:28 - 39:31and the number I'm seeking went down by one,
-
39:31 - 39:34and that tells me all the ways I can pick three people to go with Bob.
-
39:34 - 39:38The ones that don't include Bob, and Bob's just out of the running,
-
39:38 - 39:42I look at the remaining population which is still one smaller, everybody but Bob, and I
-
39:42 - 39:47look for the ways I can still pick four people from there.
-
39:47 - 39:51So what I have here is trying to compute C of NK
-
39:51 - 39:56is trying to compute C of N minus one K minus one and add it to C of N minus one K.
-
39:56 - 39:58So this is
-
39:58 - 39:59picking
-
39:59 - 40:02friends to accompany Bob. This is picking people without Bob.
-
40:02 - 40:03Add those together,
-
40:03 - 40:08and I will have the total number of ways I can pick K things out of N.
-
40:08 - 40:13So we're very much relying on this recursive idea of if I had the answer
-
40:13 - 40:16- I don't feel smart enough to know the answer directly,
-
40:16 - 40:20but if I could defer it to someone who was actually willing to do more
-
40:20 - 40:23scrutiny into this thing, if you could tell me how many groups of three you can
-
40:23 - 40:28join with Bob, and how many groups of four you can pick without Bob, I can tell you
-
40:28 - 40:30what the total answer is.
-
40:30 - 40:33The simplest possible base case
-
40:33 - 40:38we're gonna work our way down to are when there are just no choices remaining at all.
-
40:38 - 40:40So if you look back at my
-
40:40 - 40:43things that are here, in
-
40:43 - 40:46both cases the population is getting smaller,
-
40:46 - 40:50and in one of the recursive calls, the number of things I'm trying to
-
40:50 - 40:52pick is also getting smaller.
-
40:52 - 40:55So they're both making progress toward kind of shrinking those down to where
-
40:55 - 40:56there's kind of
-
40:56 - 40:59more and more constraints on what I have to choose.
-
40:59 - 41:02For example, on this one
-
41:02 - 41:05as I keep shrinking the population by one
-
41:05 - 41:07and trying to get the same number of people,
-
41:07 - 41:10eventually I'll be trying to pick three people out of three,
-
41:10 - 41:14where I'm trying to pick of K of K remaining. Well, there's only one way to do
-
41:14 - 41:17that which is to take everyone.
-
41:17 - 41:18On this one, I'll
-
41:18 - 41:20eventually keep picking people,
-
41:20 - 41:23so the K is always less than the N in this case. The
-
41:23 - 41:26K will eventually kinda bottom out, or I'll say I've
-
41:26 - 41:30already picked all the people. I've already picked four people. I need to pick zero more. And
-
41:30 - 41:34at that point, that's also very easy, right? Picking zero out of a population, there's
-
41:34 - 41:38only one way to do that.
-
41:38 - 41:41So what we end up with is
-
41:41 - 41:45a very simple base case of if K equals zero - so I'm not trying to choose anymore.
-
41:45 - 41:49I've already committed all the slots. Or if K is equal to N
-
41:49 - 41:50where I've
-
41:50 - 41:52discarded a whole bunch of people, and now I'm down to where I'm
-
41:52 - 41:56facing I've gotta get four, and I've got four left. Well, there's only one way to do those things,
-
41:56 - 41:59and that's to take everybody or to take nobody.
-
41:59 - 42:00And then otherwise,
-
42:00 - 42:02I make those two recursive calls
-
42:02 - 42:05with Bob, without Bob, add them together
-
42:05 - 42:13to get my whole result.
-
42:13 - 42:14That's wacky. I'm gonna
-
42:14 - 42:18read you something,
-
42:18 - 42:22and then we'll call it a day. I
-
42:22 - 42:31brought a book with me. I stole this from my children.
- Title:
- Lecture 8 | Programming Abstractions (Stanford)
- Description:
-
Lecture 8 by Julie Zelenski for the Programming Abstractions Course (CS106B) in the Stanford Computer Science Department.
Julie talks about solving problems recursively. She covers functional recursion with the simple example of writing an exponential function using recursion. From the simple program performing as an exponential function Julie continues to show a more efficient recursion code. The next example she covers is that of binary search and how recursion is used in this instance.
Complete Playlist for the Course:
http://www.youtube.com/view_play_list?p=FE6E58F856038C69CS 106B Course Website:
http://cs106b.stanford.eduStanford Center for Professional Development:
http://scpd.stanford.edu/Stanford University:
http://www.stanford.edu/Stanford University Channel on YouTube:
http://www.youtube.com/stanforduniversity/ - Video Language:
- English
- Duration:
- 42:37
N. Ueda edited English subtitles for Lecture 8 | Programming Abstractions (Stanford) | ||
Eunjeong_Kim added a translation |