Okay, so let's move on, and actually discuss the pseudo-code for the merge sort algorithm. First, let me just tell you the pseudo-code, leaving aside exactly how the merging subroutine is implemented. And thus, high levels should be very simple and clear at this point. So there's gonna be two recursive calls, and then there's gonna be a merging step. Now, I owe you a few comments, 'cause I'm being a little sloppy. Again, as I promised, this isn't something you would directly translate into code, although it's pretty close. But so what are the couple of the ways that I'm being sloppy? Well, first of all, there's, [inaudible], you know, in any recursive algorithm, you gotta have some base cases. You gotta have this idea that when the input's sufficient. Really small you don't do any recursion, you just return some trivial answer. So in the sorting problem the base case would be if your handed an array that has either zero or an elements, well it's already sorted, there's nothing to do, so you just return it without any recursion. Okay, so to be clear, I haven't written down the base cases. Although of course you would if you were actually implementing, a merge short. Some of you, make a note of that. A couple of other things I'm ignoring. I'm ignoring what the, what to do if the array has odd lengths, so if it has say nine elements, obviously you have to somehow break that into five and four or four and five, so you would do that just in either way and that would fine. And then secondly, I'm ignoring the details or what it really means to sort of recursively sort, so for example, I'm not discussing exactly how you would pass these subarrays onto the recursive calls. That's something that would really depend somewhat on what, on the programming language, so that's exactly what I want to avoid. I really want to talk about the concepts which transcend any particular programming language implementation. So that's why I'm going to describe algorithms at this level okay. Alright, so the hard part relatively speaking, that is. How do you implement the merge depth? The recursive calls have done their work. We have these two sort of separated half the numbers. The left half and the right half. How do we combine them into one? And in English, I already told you on the last slide. The idea is you just populate the output array in a sorted order, by traversing pointers or just traversing through the two, sorted sub-arrays in parallel. So let's look at that in some more detail. Okay, so here is the pseudo-code for the merge step. [sound] So let me begin by, introducing some names for the, characters in the, what we're about to discuss. So let's use C. To denote the output array. So this is what we're suppose to spit out with the numbers in sorted order. And then, I'm gonna use a and b to denote the results of the two recursive calls, okay? So, the first recursive call has given us array a, which contains the left half of the input array in sorted order. Similarly, b contains the right half of the input array, again, in sorted order. So, as I said, we're gonna need to traverse the two, sorted sub-arrays, a and b, in parallel. So, I'm gonna introduce a counter, i, to traverse through a, j to traverse through b. I and j will both be initialized to one, to be at the beginning of their respective arrays. And now we're gonna do. We're going to do a single pass of the output array copying it in an increasing order. Always taking the smallest from the union of the two sorted sub arrays. And if you, if there's one idea in this merge step it's just the realization that. The minimum element that you haven't yet looked at in A and B has to be at the front of one or the two lists right so for example at the very beginning of the algorithm where is the minimum element over all. Well, which ever of the two arrays it lands in -- A or B -- it has to be the smallest one there okay. So the smallest element over all is either the smallest element A or it's the smallest element B. So you just check both places, the smaller one is the smallest you copy it over and you repeat. That's it. So the purpose of K is just to traverse the output array from left to right. That's the order we're gonna populate it. Currently looking at position I, and the first array of position J and the second array. So that's how far we've gotten, how deeply we've probed in the both of those two arrays. We look at which one has the current smallest, and we copy the smallest one over. Okay? So if the, if, the entry in the i position of A is smaller, we copy that one over. Of course, we have to increment i. We probe one deeper into the list A, and symmeterically for the case where the current position in B has the smaller element. Now again, I'm being a little bit sloppy, so that we can focus on the forest, and not sort of, And not get bogged down with the trees. I'm ignoring some end cases, so if you really wanted to implement this, you'd have to add a little bit, to keep track of when you fall off, either, either A or B. Because you have additional checks for when i or j reaches the end of the array, at which point you copy over all the remaining elements into C. Alright, so I'm gonna give you a cleaned up version, of, that pseudo-code so that you don't have to tolerate my questionable handwriting any longer than is absolutely necessary. This again, is just the same thing that we wrote on the last slide, okay? The pseudo-code for the merge step. Now, so that's the Merge Sort algorithm. Now let's get to the meaty part of this lecture, which is, okay, so merge sort produces a sorted array. What makes it, if anything, better than much simpler non divide and conquer algorithms, like say, insertion sort? Other words, what is the running time of the merge sort algorithm? Now I'm not gonna give you a completely precise definition, definition of what I mean by running time and there's good reason for that, as we'll discuss shortly. But intuitively, you should think of the running time of an algorithm, you should imagine that you're just running the algorithm in a debugger. Then, every time you press enter, you advance with one line of the program through the debugger. And then basically, the running time is just a number of operations executed, the number of lines of code executed. So the question is, how many times you have to hit enter on the debugger before the, program finally terminates. So we're interested in how many such, lines of code get executed for Merge Short when an input array has n numbers. Okay, so that's a fairly complicated question. So let's start with a more modest school. Rather than thinking about the number of operations executed by Merge Sort, which is this crazy recursive algorithm, which is calling itself over and over and over again. Let's just think about how many operations are gonna get executed when we do a single merge of two sorted sub arrays. That seems like it should be an easier place to start. So let me remind you, the pseudo code of the merge subroutine, here it is. So let's just go and count up how many operations that are gonna get used. So there's the initialization step. So let's say that I'm gonna charge us one operation for each of these two initializations. So let's call this two operations, just set i equal to one and j equal to one then we have this four loop executes a total number of end times so each of these in iterations of this four loop how many instructions get executed, well we have one here we have a comparison so we compare A(i) to B(j) and either way the comparison comes up we then do two more operations, we do an assignment. Here or here. And then we do an increment of the relevent variable either here or here. So that's gonna be three operations per iteration. And then maybe I'll also say that in order to increment K we're gonna call it a fourth iteration. Okay? So for each of these N iterations of the four loop we're gonna do four operations. All right? So putting it all together, what do we have is the running time for merge. So let's see the upshot. So the upshot is that the running time of the merge subroutine, given an array of M numbers, is at most four M plus two. So a couple of comments. First of all, I've changed a letter on you so don't get confused. In the previous slide we were thinking about an input size of N. Here I've just made it. See I've changed the name of the variable to M. That's gonna be convenient once we think about merge sort, which is recursing on smaller sub-problems. But it's exactly the same thing and, and whatever. So an array of M entries does as most four M plus two. Lines of code. The second thing is, there's some ambiguity in exactly how we counted lines of code on the previous slide. So maybe you might argue that, you know, really, each loop iteration should count as two operations, not just one.'Cause you don't just have to increment K, but you also have to compare it to the, upper bound of N. Eh, maybe. Would have been 5M+2 instead of 4M+2. So it turns out these small differences in how you count up. The number of lines of code executed are not gonna matter, and we'll see why shortly. So, amongst friends, let's just agree, let's call it 4M plus two operations from merge, to execute on array on exactly M entries. So, let me abuse our friendship now a little bit further with an, an inequality which is true, but extremely sloppy. But I promise it'll make our lives just easier in some future calculations. So rather than 4m+2, 'cause 2's sorta getting on my nerves. Let's just call this. Utmost six N. Because m is at least one. [sound] Okay, you have to admit it's true, 6MO is at least 4M plus two. It's very sloppy, these numbers are not anything closer to each other for M large but, let's just go ahead and be sloppy in the interest of future simplicity. Okay. Now I don't expect anyone to be impressed with this rather crude upper bound, the number of lines of code that the merge subroutine needs to finish, to execute. The key question you recall was how many lines of code does merge sort require to correctly sort the input array, not just this subroutine. And in fact, analyzing Merge Sort seems a lot more intimidating, because if it keeps spawning off these recursive versions of itself. So the number of recursive calls, the number of things we have to analyze, is blowing up exponentially as we think about various levels of the recursion. Now, if there's one thing we have going for us, it's that every time we make a recursive call. It's on a quite a bit smaller input then what we started with, it's on an array only half the size of the input array. So there's some kind of tension between on the one hand explosion of sub problems, a proliferation of sub problems and the fact that successive subproblems only have to solve smaller and smaller subproblems. And resolute resolving these two forces is what's going to drive our analysis of Merge Short. So, the good news is, is I'll be able to show you a complete analysis of exactly how many lines of code Merge Sort takes. And I'll be able to give you, and, in fact, a very precise upper bound. And so here's gonna be the claim that we're gonna prove in the remainder of this lecture. So the claim is that Merge Short never needs than more than six times N. Times the logarithm of N log base two if you're keeping track plus an extra six N operations to correctly sort an input array of N numbers, okay so lets discuss for a second is this good is this a win, knowing that this is an upper bound of the number of lines of code the merger takes well yes it is and it shows the benefits of the divide and conquer paradigm. Recall. In the simpler sorting methods that we briefly discussed like insertion sort, selection sort, and bubble sort, I claimed that their performance was governed by the quadratic function of the input size. That is they need a constant times in the squared number of operations to sort an input array of length N. Merge sort by contrast needs at most a constant times N times log N, not N squared but N times log N lines of code to correctly sort an input array. So to get a feel for what kind of win this is let me just remind you for those of you who are rusty, or for whatever reason have lived in fear of a logarithm, just exactly what the logarithm is. Okay? So. The way to think about the logarithm is as follows. So you have the X axis, where you have N, which is going from one up to infinity. And for comparison let's think about just the identity function, okay? So, the function which is just. F(n)=n. Okay, and let's contrast this with a logarithm. So what is the logorithm? Well, for our purposes, we can just think of a logorithm as follows, okay? So the log of n, log base 2 of n is, you type the number N into your calculator, okay? Then you hit divide by two. And then you keep repeating dividing by two and you count how many times you divide by two until you get a number that drops below one okay. So if you plug in 32 you got to divide five times by two to get down to one. Log base two of 32 is five. You put in 1024 you have to divide by two, ten times till you get down to one. So log base two of 1024 is ten and so on, okay. So the point is you already see this if a log of a 1000 roughly is something like ten then the logarithm is much, much smaller than the input. So graphically, what the logarithm is going to look like is it's going to look like. A curve becomes very flat very quickly, as N grows large, okay? So F(n) being log base 2 of n. And I encourage you to do this, perhaps a little bit more precisely on the computer or a graphing calculator, at home. But log is running much, much, much slower than the identity function. And as a result, sorting algorithm which runs in time proportional to n times log n is much, much faster, especially as n grows large, than a sorting algorithm with a running time that's a constant times n squared.