 Alrighty, welcome back to 105. So today, this lecture is fun because it's more sorting and it's nothing that you'll be tested on. So we get to have fun today. So we will touch on one more sorting algorithm. The other sections are behind us. So I will teach you something useful. So don't have to take notes. If you don't want, we can just sit back and enjoy. So I put in yellow. Anything in this lecture is not testable. So we're going to go over three sorting algorithms. One really good one and two unbelievably stupid ones. So first one, we will do a good one. So quick sort, all the sorting algorithms we saw before were like N squared. So they were really, really slow. If you remember that graph, quick sort is a sorting algorithm that is N log N. And how it works is we select an element as a pivot. So we just select one element. So in the case their numbers, we select one number. And our goal is we want to place everything less than the number to the left of that number and everything greater than that number to the right of the number. And this is called partitioning. And then after this, we can use our knowledge of recursion. And now we kind of separated the problem into two smaller sub-problems. So we can now sort the left side of the pivot because they're all less than that number and also sort the right side of the pivot because they're all greater than that number. So they are kind of already sorted. And that is how the algorithm works. So the main part of this is called partitioning and we can walk through it really quick. So here is our array we would like to sort. It is eight numbers. So the in the C's are zero all the way up to seven. And those are all the values. So 10, 14, eight, 13, 20, three, six and nine. So it is definitely not sorted. So how this algorithm works is it chooses a pivot. So you can choose it as the first element, the last element, the initial pivot as the middle element. Doesn't matter in terms of making the programming task a little simpler. We will choose the last element as the pivot. So our goal here is we're choosing nine as a pivot. So we want to move it somewhere in this array and swap any values we need to, such that everything to the left of nine is less than nine and everything greater than nine is to the right of nine. So we want to get all the small numbers smaller than nine somewhere to the left. All of the numbers larger than nine to the right. And then we want to place nine wherever it goes. So how the algorithm will work is I will use a green arrow that will just signify where we want to place nine. So initially we would place nine at the beginning. If we just go ahead and swap nine at the beginning, that should mean that while there is nothing less than nine and everything else should be greater than nine. So clearly it won't be the case here but that is our initial starting. We're going to use this green arrow to represent where the pivot should go. So how this will work is we will iterate over every single element, not including the pivot. And if the value is less than the pivot, we're going to swap it to wherever the green arrow is pointing and then move the green arrow one over to the right. And then the idea is once we're done, well the green arrow points to where the pivot is. So we will just go ahead and swap nine, like the last element, wherever the green arrow is and then suddenly that will be our partition. Everything to the left will be less than, everything to the right will be greater than. So everything will be comparing relative to the pivot. So first we would compare element zero to the pivot. So 10 is greater than nine, so we don't swap anything, we don't adjust the green arrow, we just leave it as is. And then check the next element, 14, also greater than the pivot, so we leave it alone. Next thing we compare, we compare it to index two. The element there is eight, which is less than the pivot. So we want to make sure that once we are done, that eight is to the left of nine. So what we're going to do is we are going to swap eight. So we're currently comparing to index two. So we're going to swap index two with whatever the green arrow is pointing to, index zero. So after we swap, it will look like this and we update the green arrow. So we swapped index two and index zero and then updated the green arrow. So this means if we stopped right now, we would go ahead and swap in nine with whatever the green arrow is pointing to and then, well, eight is to the left of the pivot because it's a smaller number. All right, on the same page with this, we're all good. All right, so that was us looking at element two. Next time we're going to look at element three, 13 greater than the pivot, not going to do anything. 20 greater than the pivot, not going to do anything. Five less than the pivot, so we're going to do the exact same thing. So we're going to swap the index five with now whatever the green arrow is pointing to, index one and then update the green arrow to go forward one because we want to move, that's where we would want to put the pivot. So we swapped three in, swapped three and 14 and then update the green line. So now if we stop now, we would place nine here and then everything to the left is less than nine. All right, last element we have to look at is six. At index six, finally enough and then it is less than the pivot. So same thing, we would swap that and 10 and then increment the green arrow and then it looks like this and then now we're pretty much done. All we have to do is swap the last element with where it should go. So nine should go where the green arrow is, so I swapped nine and 13. So now it looks like this and now it is successfully partitioned. So nine is placed at index three and then everything to the left of nine is less than or equal to it and then everything to the right is greater than or equal to it, right? It's all greater than. So then after this, we can, if we define the problem right, well we can recursively sort this side. So this would probably pick the pivot as six and then go ahead and rearrange it such that we got six, three and eight which would then be in order and then we could go ahead, have our recursive base case that like if we're sorting a single element or nothing then we can just exit and then it would be sorted. So this is quick sort. So quick sort, we can just define it using an array and array length but we're going to have a helper because our quick sort is going to take the index to start at and then the index to end at because we're going to try and sort smaller arrays. So our base case is if low is less than or equal to high because low should always be less than high and then we write partition. So initially we partition the whole thing and what the partition does is it does that algorithm and then returns the index where the pivot is. So in this case it would return the pivot as being at index three. So in this case it would return the index as three and then we run quick sort on everything from zero to while three is in the right place. So index three is at the right place. So we can sort elements zero all the way to two and then we can also sort the elements four all the way up to whatever we had seven. So then this would just run recursively over and over again and we have lots of fun. So any questions about that? We don't really have to understand this if we don't want. You will thank me for this introducing you to this later because this will come up again in an algorithms course next year probably where you actually do have to understand this. So you may or may not thank me for actually introducing it to you now. So other things, the partition code. I mean, if you did what I did in undergrad and search Google, you would probably come up with code like this and this is fairly ugly. It kind of gets the idea. So it starts, it saves the value of the partition. So in that case, our case it was nine. It does some weird stuff like it sets I which is supposed to keep track of like where the pivot goes to starts it off at low minus one, which if we're starting it off with the whole array would start off at minus like zero minus one, which is minus one, which is kind of weird. And then they iterate over every single element including the pivot because it's at high, which is weird to me. And then this is the code where it's, oh, if the element I'm currently looking at is less than the pivot, then I'm going to have to swap it in and update I. So I need to move where my pivot needs to go. And then after this whole for loop runs, which is what we did in the example, then it increments I and then swaps I and high, which is where the pivot is. So yeah, I don't like this code for several reasons. One, checking the pivot and then starting off at low minus one and then just remembering this increment after, kind of weird. So I would probably rewrite it like this. So start I equal to low, which is kind of what we did in that example when we went through it by hand. And then in the loop, I'd have J less than high, not equal to because, well, I don't need to check the pivot because I'm storing the value in the pivot variable. And then I could just check that, have a little optimization here that if the number is already the smallest number, I don't try and like swap it with itself. That would be weird. So I just checked if I does not equal J, then swap and then just like I wrote in the board, I increment after I swap things. And that saves me having to increment it after the loop. So after the loop just swaps the pivot to wherever it needs to go. So questions about that. So it actually works. So let's go ahead. If we go ahead and run this, boom, build quicksort. Hey, it actually sorts the array. Cool, recursion. So there are other recursive sorting algorithms if you really want to. If you want to Google search, there's one called merge sort. That's probably the next easiest one if you're interested in this. But quicksort is typically the one that's actually used when you're sorting numbers and sorting strings and anything like that. So quicksort is probably the first sorting algorithm. You'll see that is actually in use. So sometimes people would use insertion sort for a not that many elements, but for anything large, it will probably use something like quicksort. All right, now onto the fun joke sorting algorithms. All right, so I put it in red to never use these, but there's an algorithm called bogosort. And everyone loves bogosort because with the real life analogy, how bogosort works is if I just, you told me to sort the deck of cards. If I went, oh, okay, I know how to do this. I took the deck of cards, I threw it in the air, I picked up the cards randomly, and then I checked whether or not they were sorted. And if they weren't sorted, I just repeated it. So they aren't sorted, I throw them up in the air again, pick them up randomly, check if they're sorted. If they're not, I throw them up in the air again. Yeah. It's the big O for that. Big O for that is n factorial, average case. So n factorial, not good. So if you think of like how factorials grow, yeah, it gets really bad really, really, really quickly. So what? Like 100 factorial is, well, 10 squared is 100. 10 factorial is what, some like 3 million, something like that. So it gets pretty bad. Yeah. Yeah, so the question is, is there a chance that it is faster than quicksort? And the answer to that is yes. If the array's already sorted, you would check, is it sorted? Yes, it is, I'm done. And to check if they're array sorted, I just have to, that's like on, right? I just have to check every pair of elements. I just have to make like n minus one comparisons to check if it's sorted. So in that case, it is faster. But how likely, but if your sorting algorithm only works fast if the array's already sorted, it's a pretty crappy sorting algorithm. All right, so, bogosort sounds pretty stupid, right? Anyone think of anything stupider than that? Okay, there's one really stupid one called miracle sort. And what miracle sort does is just check if it's sorted, and then if it's not, it doesn't do anything, it just checks it again. So hopes that a miracle happens, which the running time of that is probably infinite, if it's not already sorted. If it is sorted, well then it's the fastest thing on the planet, so we're all good. All right, slightly dumber than bogosort, and do not use these on the exam. This is like the one thing where like efficiency is questionable here. But even worse, there's something called bozo sort, and that just randomly switches two cards and then checks if it's sorted, if not repeat. So bogosort, you at least throw every card up in the air for bozo sort, you just make one swap and that's it. So, that seems fun, so let's implement it. So here's bozo sort. I just wrote a function called inorder that returns a boolean whether or not the array is sorted. So to check that, that's pretty easy, right? I could start it at whatever I want. I decide to start it at one and then go to the array length because we have to check every single pair. So if the array at slot i minus one is greater than i, that means, well, it's not sorted, so I can return immediately false. And then otherwise, if I check every single thing and all the elements are greater than or equal to, then I could return true that it's actually sorted. So we can get some practice with our randomized. So how would I implement bozo sort? So I'll start it off. So I need an index that's actually valid in that array because I want to swap two random elements, right? So I could do something like this. So if I do ran and then mod array length, well, that will give me a number back between zero and array length minus one, which is a valid index, right? So that would be one index. I could write it like this, so just give me another number and then I will just go ahead and swap them. So array i, array j. All right, that looked good. Yeah, I could have an optimization, so here i can be equal to j. So maybe I don't want them to be the same thing because this is stupid, but that's kind of, that might be more than like, more stupidness than we would like to accept. So we can make this a little smarter. So what we could do is for j, it's like, well, if you think about it, the first number we can pick like array length things. For the second number, if we don't want it to repeat, we should pick up to array length minus one. And if I leave it as is right now, well, I could actually pick the same number. It could both be zero. So I probably wanna add a check here. So one check I could have is if j is greater than or equal to i, then I want to increment j. And that will make sure, so for most of this, you kind of want to consider like the starting worst case and then the middle case and then the best case. So if grand mod this gave us zero and then we got zero back from j, while we don't want them both to be zero, we would want at least the j to be equal to one. So this is now where we check. So if they're equal, then we would increment j. So we would do swap index zero and one, which makes sense. And then if, I don't know, i was zero and j was, I don't know, three or three, then well, incrementing j to be four, probably not that big of a deal. But if we got the absolute last thing here, so say our array length was, I don't know, if our array length was eight, then what we might get for j is, if we do a ray length minus one, that would be mod seven. So the biggest number we could get is six. So if we got six back, well, that should actually represent the last index, which should be seven because our array length is eight. So if we get six, yeah, we should probably just increment j so we can make sure that we actually get the last element. So this bozo sort, will it work? So here's my big array. I print the array, I bozo sort it and I print it. So will it work? Let's see. But like, as written, is there any reason why it wouldn't work? It's just one of those things, right? That's just so stupid. There's no way it fails. So yeah, we're going to unvoid for. Bozo sort. Boom, it's sorted. Do not use it. It is stupid, but it's kind of funny that it works. So any questions about bozo sort? Yeah, again, do not use it on an exam because yeah, the other sections have not gone other, like will not make it to quick sort, will not make it to this and probably don't want to use this. I mean, if you're brave enough, go for it. Do not hold me responsible if you get a zero. All right, so you've code for it? Yeah? Yep. Yeah, so the question is, isn't there a chance where I just keep swapping it and through randomness, I just never make it and the answer is yep. But due to statistics, right? There's a chance if I give you a coin and I tell you to flip heads, there's a chance you get a thousand heads in a row. Is it likely that's gonna happen? No. So this, I mean, you can calculate the odds to this and see, well, here, we can see how many times, let's see. In count, we can count how many times we actually swap. So plus, plus count. So it turns out computers are pretty fast. We can just see. Let's experiment. So for most things in computers, you can just go ahead, you can experiment yourself. That's what makes software great. You just write more software to check your software. So yeah, that randomly tried like a million times. So turns out computers are pretty fast, right? So if you wanna see how bad it gets, let's go ahead. If we add a number, let's add another number, I don't know, 50, whatever. So that was what, a million and a half attempts. So now two million, still worked, right? And then, oh yeah, so RAND gives the same thing every time. Let's go ahead and use S-RAND time. Okay, let's get some real randomness or better randomness. So that time it was seven million, four point three, five point four, oh, that was only half a million, not five million, so. But yeah, you can run, you can get statistics with this, two million, whatever. And I mean, more numbers you add. I mean, eventually your computer is not gonna be that fast, right? So let's see what my poor little laptop can do. All right, that's, how many elements is that? That's like what, 10, something like that? Okay, that's, we're probably at the limit of like randomly trying to sort this. All right, my computer's getting hot, let's see. This, there, there we go. So it is effective up to whatever that is. One, two, three, four, five. So if you only have to sort 12 numbers, go for it, kind of breaks down after 12. But I mean, who am I to tell you what to do? If you go to a job and they tell you to sort something and you use that, you're probably gonna get fired. So that's why I have a red text, never use it. All right, luckily for us, we don't have to write quicksort, so I'll show you how to actually use quicksort. It's actually part of the standard ST library. And of course, they had to make it shorter than everything else. Why would they call it quicksort? They called it Qsort. Q stands for quick. So it takes a whole bunch of arguments and it has to do with the limitations of C, but since we know how array works and we did dynamic memory and all that, well, we can kind of realize what all these are. So the first argument is called base. That is just the starting address of the array you would want to sort. Next argument is called NM. So it's supposed to be the size of a member or the size of an element, or sorry, the number of members in the array, AKA it's supposed to be the length of the array or number of elements. They like using the word member instead of elements, so don't know why. Then third parameter is size, which is the size in bytes of each element. So that's where you can just go ahead and use size of because you have to tell it exactly how the memory is laid out. And then the third parameter is something really weird. This is not a type we have gone over before. It looks really odd, but that actually just means it wants a function as the last argument. And how this breaks down is it wants a function and then just ignore the name and the star. It means you need to define a function that takes two parameters and these are their types, so it should take a const void star, aka a pointer, and then another pointer to compare them and then it should return an int. So how they want the compare function to work is quite similar to string compare. So it should take a pointer to two elements and then return whether or not they are less than or equal to each other. So if the first argument is less than the second, it should return a negative value. If they are equal, then it should return a zero. And if the first element is greater than the second, it should return a positive value. So you can just use negative one zero and one if you want. And that's the same way string compare works, right? If we wanna compare if two strings are equal, use stir comp and then check if the result is zero. So we can actually use Q sort to sort our numbers. So here we want to sort this array. So if we want to sort this array, the base is just array because of pointer decay. There are the number of members is just the length of the array. The size is the size of each element. So it's the size of int. And then we want to write a compare. So compare will take two pointers, two actual, two pointers, two elements. So because this is C, we have to tell it what the pointers actually are. In this case, if we are sorting integers, the pointers are pointers to integers. So we can go ahead and just do some typecasting. So we can say that A is actually an int star. And then we can say that B is actually a int star as well. And now these are the proper types. So now we can dereference them and get an int. So if we want to write our function, we could do if, so we can dereference what or X to get the int value. So if X is less than Y, then we know we need to return a negative value. So just to save some space, I will do this and not do a new line. And then I could check if X is greater than, well, or if X is greater than Y, then I return one and then otherwise are equal. So I could just return zero. So that is how you would write the compare function for the quick sort. So now if I run this and use Q sort, boom, it's sorted. And now makes it real easy. So if you want additional practice with these, you could write like insertion sort, but instead of going in ascending order, you can go in descending order. And well, that requires, you know, you have to make different comparisons and things like that in the algorithm, kind of a pain. If you want to do that with this compare function, well, if I want to sort it in the other order, I can just flip around the positive and negative values just like this. And then suddenly I now get it in the other order because it's using a different comparison function. And you can write this so it can sort whatever you want. You can have it sort structures, sort whatever, you just have to write the comparison function. That's all you have to do. And it will go ahead and then do all that quick sort stuff for you. So you never have to write quick sort again. And luckily this is as hard as using quick sort gets. So other languages make this a lot easier. C makes it exceedingly hard, but it's not too bad, right? Kind of. All right, any questions about that? Yeah? Yeah. Yeah, so the question is, does C have anonymous functions like Python has lambdas or anything like that? And the answer to that is no. C does not do any of that. So C plus plus will. But C, the reason why some people like it is, well, the language itself is not that big. It's not that complicated, but it's ugly. It's ugly as well. It's pretty ugly. And it makes doing some things just exceedingly hard. So in some languages, do you just give it an array and you do like array.sort or something like that? And then boom, you're done. In C, you have to tell it literally everything. You have to tell it details about memory and all that stuff. And it's just a lot of stuff. Yeah? So the question is, can I pass functions to other functions? And the answer to that is yes. So like here, I passed a function to the quick sort function, right? So, yeah. You didn't know what to do. Yep, yeah, so the syntax is just kind of ugly. So like the type of it is like that weird type, that compare type, that's what the type of a function is. It's ugly, but at the end of the day in C, it actually just is a pointer. Like functions live somewhere in memory and it's really just a pointer. But we don't go into that much detail. But once you take the next course in the series and like do assembly, that's like closer to the CPU. Yeah, functions, you just need an address for a function. So luckily we do see because well, if you understand C, then you understand kind of how a computer works. All right. So another example, that quick sort kind of makes it kind of nice. So let's go ahead and solve another problem. Here, I wrote main that takes a bunch of arguments. So I can type in whatever I want. And while using quick sort and I have a string compare, shouldn't take me much code if I want to go ahead and sort all the arguments. So I can sort them in alphabetical order. So let's see if I run this. Let's say John, John Elf, A, Z, I don't know. Hello, something like that. So here, remember the first zero with argument is just the name of the program, so I'll ignore it. So if I go ahead and just print them, they'll show up in the order I typed them. And if I want to go ahead and sort them all, well, we could write insertion sort, we could do whatever but then it's kind of a pain and then like, how to compare strings, oh, we have string comp, we have to remember how to call it, da, da, da, da, da, kind of a pain. So we can try it with quick sort. So we just type Q sort and then we need the starting address of the array. So if I want to ignore the first element, I could do argv plus one, right? So I'm just doing pointer arithmetic, skipping the zero with argument, so I want my array to technically start at the second element, right? So I want to skip the first one and then the number of elements, well, that's given by argc and if I skip the first one, it's minus one, right? So if I type in two, like if I type in here, I would have this would be like the first argument, zero with argument, one, two, three, four, five. So I only, so the size would be six. So I only really want to sort the five I actually typed, so I'll just skip the first one. So I will do minus one. And then here it says the size, so it's the size of each element. So the size of each element, well, they are a char star, so it's a pointer to the first character of the string, right? That's null terminated if we remember our strings. And then what's our next argument? So now we need a comparison function and I just wrote a little stub there. So that would be our quicksort and now we have to write compare. So you might think that I could just, like just use string compare, right? Do something like that. And that should work kind of. So you can go ahead and try this and not sorted, not in the least bit. So why is that? Well, remember that the pointers here, they are pointers to elements. And if our element is a char star, if it's a pointer to an element, it is a pointer to a pointer to a character, right? So in other words, we would have a char pointer pointer called x and that's what actually, oops, that's what actually A is. And then B is actually a char pointer pointer two. So to access the pointer of the string, well, I have to de-reference that, right? And you can remember de-referencing just kind of takes a star off of the type. So if I de-reference x, then I will get a char star back. So pointer to a character, which will point to a null terminated string. So I could do something that looks like this. Oh, not z, why? So now if I run this, boom, it's sorted. It didn't actually have to do that much work, right? It just looks potentially very, very ugly. But if you know C and you know what's going on, it's not too bad and this is about the hardest thing we will get in this course, yeah. Yeah, so I, well, the crux of the question is why before when I just used string comp, did the order not change? And the answer to that is basically the pointer was pointing to like a bunch of pointers and all of them kind of started with the same prefix and then eventually this array, I believe ends with a null, like a null pointer. So eventually they all had the same prefix and they all ended with a zero. So the pointers just kind of grew so it would essentially just be the same order that I had to find the pointers in just by chance, essentially. So the only reason why I got the same order is essentially more or less by luck because my computer's laid out memory in that exact way and I interpreted the addresses wrong because they were actually pointers to pointers but I interpreted them as strings so potentially anything could have happened including it could have crashed but it just turned out that didn't do anything. So yeah, debugging, that would have been very, very difficult if it like kind of worked sometimes but yeah, any questions about that because that's about the hardest thing we will ever see in this course. And like I said, this is completely extra so if you need to write a program, I would suggest not writing your own sorting algorithm and using Q sort even though it's a kind of a pain in the butt to use. Just remember the rules where this compare function, they are pointers to elements so if you want to access an element you have to de-reference it so that's the only thing you really have to remember. All right, so here's the code so you have it in slides and then yeah, typically from here on out it's good to practice writing sorting algorithms it's really good practice in C especially since small errors will just produce the wrong result or you'll have memory errors and it'll crash but likely in any real program you'll probably want to reach for something like quick sort and it's a fairly difficult function to use but still better than writing quick sort by hand. You kind of really have to understand memory the limitations of C because C has to use like a void star to be general which AKA just means it's a pointer to something C doesn't know what it is and you kind of have to tell C what it is and cast it and that's really error prone. So the primary design for a language like C++ that you will use later and pretty much any other language that's not C is to make things like sorting really much, much easier to do and also much more efficient so you don't have to do all that weird stuff we had to do before. Usually like I said before you can just like take an array and then do array.sort and then boom, it works but this is how computers actually work at some point during that a quick sort is going to happen. So with that I will stick around if you have any other questions but just remember pulling for you we're all in this together.