 Welcome back to 105. So good to see everyone again after missing two lectures. So now we get to talk about link lists, which is, at least from talking to previous students that have taken this course, one of the hardest things in this course, and this is where some of them start to struggle. But hopefully if you've understood pointers up to this point, we will be okay. So we will hopefully get through this together. So title of the lecture is, what is a link list? And I will start with motivating why we would want to have a link list. So arrays, they work. They're not perfect. They have some drawbacks. We'll see more as we go on. But the first drawback could simply be that let's just, we have an array that represents like a line of people in line. Maybe you're taking turns for, I don't know, your favorite childhood toy or something like that with all your siblings. You get assigned a number. Say we have four people in line. They get assigned number one, two, three, and four. And you just take turns in order. So the order of people will just use an array to represent that line. And the order they are in the array is the same order they are in that line. So whoever is currently at index zero, so whatever is, the first number at that array is who is at the front of the line. So if we want to, letting one, two, three, or one, two, three, four, I can count. And we print our array. We want to write a function called move array that just takes whoever is at the beginning. So currently whoever is at spot one or number one gets thrown to the back of the line. So after this move array function, our array should be two, three, four, and then one. So I've already written it. So we should know how to write the print array function. So it just prints everything in order. Shouldn't be a big surprise to anyone. And then I write this move array function which goes ahead, tries to essentially say, okay, number one, you've had your turn. So number two is next, then three, then four, and then number one is at the back. So if I want to write this move array function, turns out it is a bit of a pain. So my move array function, well, I would first check that the array length is one or greater. If it's just one, I don't have to do anything. If it's zero, don't have to do anything. So I will just check for corner cases first. And then in order to move everything in the array, I have to do a lot of assignments. So first, if we go back to our swap example where I need a variable to keep track of the element, I was assigning something to, so I didn't overwrite it and lose it. Same idea here. So I will get whatever number is currently at the front of the array. So it would be at index zero. And then for index zero all the way up to array length minus one. So I skipped the last element. I will just essentially shift the numbers by one. So array one or sorry, array i will be equal to array i plus one. So here is the array we had before. So if I go through that, I save the number one and then first time through the loop, I assign two equal to the index zero. So then these would both be two. Then I assign three to here. Now these are both three. Then I assign four to here. Then they are both four. And then at the final step, I set the last element to whatever was the first before. And there's a question, why is there no return after the last line of the move array function? So this is just a function. Void means it returns no value. It's just optional. I could write return here. C won't care, but C inserts that for us anyways if we don't want to. Usually people don't write return at the end of void functions because C will just do it for you. Essentially extra code we don't have to write. All right. So any questions about that move array function? All right. Well, I have a question for you. So how many assignments did I do? I saw five. We got five. Yeah, we did five assignments, right? We assigned something equal to first and then we reassigned every single array element. And let's say my array had 10 things in it. How many assignments do I get to do then? 11. And then it gets up and up and up and up. And doing assignments might be slow, might be kind of a pain. In general, in computing, we do not want to have something like that. So here's that code. So that had a lot of assignments. So every time I shift by one, I need to do like n plus one assignments where n is the length of the array. So in general, in computing, if you can help it work to scale with like the size of an array, you would like it just to be as quick as possible. Hopefully you just swap two things, do a known amount of work the same time every time, and we try and have it not to scale. So we'll talk about later up in the course and there's going to be a whole another course that follows it. So in order for you to analyze algorithms like this and decide which one you would prefer, try to think of better algorithms that are more efficient. For now, this course we're just solving problems, but that is something to look forward to in other courses where you actually get to analyze these algorithms. But in general, we want to do the least amount of work as possible because that would be faster. So we might notice something with our array here. So when we ran it, we might notice that well, at least to us, a lot of things stay the same. So aside from moving one to the end, everything's in the same order, right? Well, it goes to or to in the same order that stays the same. So that is basically the idea for a link list. So the order between the numbers don't change except for the one we're trying to move. So for each number, what we can do is we can keep track of which one is next. So two can know that three is next, three can know that four is next, and then well, to change everything, well, we could just put one after four and that would be it. So for each number, we track who is next in line and for the list, we might also just keep track is who is at the front of the line and it's like the equivalent of having an ordered list. So the idea behind that is for this array, instead of just having it as an array, if we want to represent it as a list, we would say one is at the front. So we know one's at the front and then one knows, well, the next number after me is two, two knows, the next number after me is three, three knows, the next number after me is four, and then if I want to put one at the back of the list, all I need to do is say, okay, well, now two is at the front of the list and one, you're now after four. So I could do that as a constant amount of work. So that is my idea behind a link list. So the data structure we're going to implement a link list is called a node. So we'll create a node in C that contains our data and then which node is next. So here is what it would look like. So we did structures before I left. So we could type def struct node as node underscore T if we want to say node underscore T instead of typing struct all the time. And then for the actual definition of the node struct itself, well, the data we only care about is just an integer. So we'll just call it the value. And then here is the next pointer. So it's going to point, which is why we need to learn pointers before this to whatever node is next. So in the previous example, we saw one node would have the value two and its next would point to a node that has the value three. So note, well, I need this to be a pointer, because if I didn't, that would be another example of fun recursion, right? So I can't define the struct in terms of itself. Otherwise that would be like the, what's the definition of recursion, C recursion. So if I didn't put a pointer here and it was node underscore T, well, that would be infinite recursion. You can't define it in terms of itself. So that's one reason why it has to be a pointer. And this next variable being a pointer also allows us some flexibility. So we will use the value null, which just means it's not some valid value to represent that there is absolutely no next node. So if you're at the back of the list, your next pointer is going to point to null to indicate that you are the last one. And then if I wanted to track what node was at the front of the list, I could just go ahead and create a variable called head to represent the front of the list. That's normally what people call it, a pointer to a node. So visually, how would that look? So it would look like this. So instead of just creating an array of size four and then it's an array, they just get assigned based off their index, I would create a node for each individual number. So in the green boxes, that is the entire structure itself. So the green would be the node and each node has a value and then a next pointer. So first, I would have the node that has the value one, then it's next would point to the node with the value two, then it's next would point to the node with the value three, then it's next would point to the node with the value four, and then it's next, I'm just showing an arrow to nothing to represent null. That just means it's not pointing to anything. It is the last thing at the back of the list. And then head just keeps track of who is at the front of the list. So that would point to the first node with the value one. So any questions about that, there is a lot of pointers going on. So hopefully, we understand pointers. So we can think for a second that what changes would I need to make to these pointers if I want to send number one to the back of the list. So first change would be I want head to point to two. And then I want four's next pointer to point to one. Yeah, and then we want one's next pointer to point to null to indicate it is now at the back. So I had to change three values there. I had to change head. I had to change head. I changed whatever was at the back of the list next to point to the previous head. And then I had to change one's next to be null. So that was three. Yeah. Yeah, so there's a question. Can I do it in that order? And that's where the implementation gets a bit tricky. So I might need to have some temporary variables to keep track of things so I don't lose them. And that's usually where people start to have difficulty, but we'll do it hopefully slow because we will have to work up to this because eventually we want to take this and then move it to that. So those are the three changes you described to me there. And no matter how many things are in this list, if this was 10 things long, it's still only three changes. So that is the main advantage for us. And yeah, through this question, yeah, there's some implementation details here where we might have to, you know, go through the list and find out what is at the back. But in terms of assigning new values, we only have to assign three values in this case. And there's going to be different ways to get, to make this work better if you care about the back of the list at all. So I have written out that struct for you and you might notice it's a bit weird if we want to define it all at once. So we could go ahead, we could create a node called node four. And remember before when we are here, I'll make that a bit bigger. When we are, if we just want to set the value of a struct, we can use the good old curly brackets. And then the values you set will be the same orders as the fields in the struct. So this is just shorthand to create a node called n4 with the value four and the next being null. So that indicates it's at the back of the list. And if I want to actually construct things and not have to play with the next point or anything like that, I have to do it backwards because, well, node three needs to point to node four. So node four already has to exist. So if I want to create it myself, I have to go backwards. So if I want to create my link list, I could just do this. So now I'm setting its next pointer. So its next pointer should be the address of n4. So that would create node three. So with the value three, that points to node four. So that would be next. So is everyone okay with me creating that? I can create the rest of them. All right. So I can create the rest of them. So good old copy paste. So there's all the rest of the nodes. I did not change the values. So boom, boom, boom. So there, I created the rest of the nodes. And then the only thing missing is the head pointer or what is pointing to the first element of the list. So I could go ahead, create a pointer to a node, call it head, and then assign it equal to n1. And then that is my link list. Right. Okay. Well, what if I wanted to print everything in this link list in order? So let's try and write a function that's just called print. I'll just call print list for now. So we want to print the contents of the list. All right. Anyone want to help me write this function? So get the current value of head. So dereference head and get that value. So if I dereference head, then that is going to be, yeah, so I can dereference head and get the value. But syntax, whoops, is the same as before just doing the arrow. It's the same thing, right? So what do you want me to do with this? Print that value. Okay. So let's see. Sorry. Yeah. So if the next value isn't null, I want to go and print the next one after that. So this kind of feels loopy. Yeah. Yeah. So I could also do while head does not equal to null. So if head's null, that means there's nothing in the list or I'm currently updating that value. And then in here, I can go ahead, print that value. So this syntax generally frowned upon with the dereference and then access the value. Perfectly fine if you want to do it, but it's usually preferred to do something like this. And then to get the next head, all I have to do is do head equals to head next. If I wanted to, I could have created a new variable. I could have just created a new pointer to a node called it current and set it to head. If I wanted to maybe rename this, oops. Yeah. To rename this, maybe I could do something like that. And then print f, a new line at the end. And that should print everything in the list, right? All right. Let's make sure it actually works. So now prints us off everything in the list, right? So what it's going to do is current starts off pointing at node one. So that is not null. It's a valid pointer. It's somewhere in memory. In fact, it's on the stack for main. Maybe want to change that later and then goes ahead, prints its value and then updates the pointer current to point to currents next. So it would start off with one and the next would point to and two. So now next time through the loop, we'd print off the value two. Then we would update our current pointer to point to twos next, which is three. That's still not null. Then we'd print it three update current to point to threes next one, which is four. Then we'd go ahead print four. Then we'd update current to point to fours next one, which is null. Then it's going to go here, check. Oh, well current is equal to null. So it's going to just exit the while loop and we're going to be done. So anyone have any questions about this print list function? Give you another minute to look at it. It uses, I mean, lots of pointers going on here, right? So more pointers, more fun. I should trademark that. It's pretty good. Yeah, so question, can I do that recursively? Of course. Yeah, we can absolutely do that recursively if you want. Because yeah, the base case is just if it's the pointer is null, don't print it. And then after that here, do we want to, all right, how many of us want to write a recursive one or do it yourself later for fun? Do we want to write it like three? Do we want to do it for fun later? Like no one? Okay, well, that's a lot of votes. Hell yeah. All right. So I could do print recursive and then whatever. I'll just start off by using just some generic name. So how would I print this recursively? So everyone remember recursion? You should probably start with a base case. And then our recursive step. So what is our base case? Yeah, I can't hear that well if I heard null. Everyone probably that if the pointer, the node pointer is null, probably a base case. I don't have to do anything if I'm printing anything right. So that's probably a good base case. So if node is null, then just return don't do anything because this is a print function, right? All right. So what would my recursive step be if I know that? Well, the current node is not equal to null. Any guess? No? Yeah, you yeah. Yeah, so I could just print the value. So that would be the current value. Or sorry, I just called it node my bed. And then while we could call print recursive with node next. So just print off the rest of the array, right? So let's see. Print recursive. So now it won't look the same because I didn't put list in front of it. But if I run it, should be the same idea, right? If I want to put the new line, I could put the new line here if I want printf new line in there, print off the list in order. If I wanted to put the list in front of it all the time, I could write like a little helper function for me that would print off list and then do that recursive function won't bother with it for now. But same same idea, I can use recursion for this. So any questions about that? That was bonus. All right, sweet. So let's see. So you have it in your slides, how we create our list in C. And when we did that step by step, what we're doing, created a node for next pointed to nothing. And now while that exists, so we can point to it so we can create the node with the value 3, point our next to it, create the node with the value 2, point our next to it, create the node with the value 1, point our next to it, and then head could point to the node with the value 1 in it. So that is how we created our list step by step. And we've already written this function. So to print the list of values. In fact, yeah, you beat me. I said you can either do it iteratively, so with a loop or recursively. I was gonna leave the recursive one for you later, but hey, we did it. So yay. So here is just so you have it on the slide. So when we printed it iteratively, that's essentially what we did. So we have some things we can solve like knowing dynamic memory and knowing structures. We can write some helper functions to make our life easy or more difficult depending on your preferences because we could create a node dynamically using malloc instead of just doing local variables within a function that die whenever the function ends. So generally for data structures and link lists and stuff like that, they'll outlive the function or if I just allocate a variable in a function, it's going to go away. So I don't want it to go away. So I will go ahead and create these nodes dynamically and also more importantly, maybe I don't want to have to name every single one of them. I don't want to have any weird memory issues with stacks or any of the other challenges we saw before with link lists and having so many pointers, it just makes it even messier if you screw up your pointers. So if I want to create a node dynamically, I could write a little helper function just called create node. So I just have create node, all I care about is whatever the value I want to set in that node. So what I will do in that function is just malloc the size of a node, so I'll request memory for it. I will get back a new pointer that points to a number of bytes large enough to hold this node underscore t data structure. I will check that it did not return null because if it returned null, that means my computer is out of memory and that means probably the world is on fire. So I should just exit my program just with that exit failure to indicate that well, my computer ran out of memory, I won't try to execute my program any further. Go ahead and set the node's value equals to the same value I got in this function and then I'll just initialize its next is equal to null and then return node which would be a pointer that I got back from malloc that I of course will free later when I'm done with it because we should always free our pointers. So any questions about that function before we write some more interesting ones? Okay, so now we can go ahead and create a function just going ahead and setting pointers myself was kind of a pain. So maybe I want to just create a function to insert to the front of the list. So this is just for constructing lists not just swapping things around. So we're just dealing with creating a node and putting it at the front of the list. So this function should do a few things. It should, well, one create a node with the value provided. We just wrote that create node function so that will go ahead get memory from the heap and all that fun stuff. And then it should do a few other things on top of that. So it should make the next pointer of that node point to whatever is currently at the front of the list because now it is next inline. So we will point to the current head and then we will make the list head point to this new node and then we'll go ahead we'll also return a pointer to the new node just in case we need it. So the function prototype will be this. We'll call it insert front. The first argument will be the head of the list. The next will be the value and it will return a pointer to that. So let us try to write it. So actually you're here. Let's try and write it and I will use. Okay, so here is our insert front function. So how would I go about writing this provided I have, where is it? So I have this create node function already created for us. So what were our steps? So we want to create a new node with the value provided. So how should I do step one? I wrote a function called create node. Hopefully I use that function, right? All right, we're still getting used to me being back and being awake. All right, so first thing. I should create a new node. So I'll create node with the value that I get from this function. Right, so now I have that node. Cool. So what should I do next? So I want to insert this node to the front of the list. Take, sorry, take heads next and point it to that? Sorry, can someone pass it up? I can't hear. The next value of what? A node? Oh, okay, so yeah. So the next value of the node I just created should point to the current head, right? So that is currently what was at the front of the list. So that is step one and then step two is I should make the head of the list point to this node, right? So step two would just be, oops, not this node. So those should be my three steps, right? And then at the end I could just return a pointer to the node. All right, anyone see any problems with that? Yeah, yeah. So we'll see if anyone picks up on that too. So here's a function. So currently head is equal to null. So I'll do the insert front. So if I do that what should happen is the current head is null. So we create a new node and then we'll set the next node equal to null. That's fine. That's the only thing on the list. And then set the head, what is that, the front. Currently equal to that new node. So if I just do insert front I should create a node with the value four on it and then that should be the only thing on the list. So if I go ahead and print the list I should see that four is in it, right? So I'll see. So this will definitely work, right? I'm a great programmer. Boom. So anyone that didn't hear the other one know what's going on or someone else in the front want to explain what the hell? Yeah. So I assign head to null and so we can, yeah one guess might be that I set head to null here, right? So maybe I just want to debug it really quick. I'll do a print f and I'll just print off whatever the value like the pointer value of head currently is. So if it's null it'll tell me that it is null. If I go ahead and run it tells me that's a natural pointer to something. It's not null. Yeah. You're good. Yeah. So what's happening here is it see function arguments. They're called by value or they get a new copy of it. So my insert front function, remember what happened before if it was an int or anything. Whenever we call this function it gets a new copy of this pointer. So this head is different than this head. So they're completely different. So any modifications I make to it do not reflect, do not reflect in main's head, right? If I wanted to go ahead and change it, well it would have to look like scanf. So I would have to give it the address of whatever it wants to change. So typically one way to go about it is just to go back by the compiler warnings. So like scanf I need to give it the address of head if I want it to modify it, which makes this not a pointer, it makes it a pointer to a pointer, right? Because every time we do address of that essentially adds another pointer to our type. So now I would have to dereference it here. So to get the current value I dereference it to see whatever that value of the pointer is at that memory location and then here I could dereference it as well and do something like that and then my list works again. So typically for this looks a bit ugly, although some of you may love, you know, a pointer to a pointer, but for this we might want to take advantage of the fact that head actually signifies something to us, it actually represents something. So knowing the head that kind of represents our whole link list. So we can make it a bit more readable by just creating another struct. So I will just create a struct called link list and in my link list struct, well I'll type def it so I can also call it link list underscore t and I will just give it one variable and it will be the head variable. So it will be a pointer to whatever is at the front of the list and the only difference now is that I'm just going to name it link list. So now with that change I could go ahead and I could do the same thing as before with my node. So I can go ahead and create it dynamically as well. So just like with my node I'll just malloc enough space for it, which essentially is just the pointer. Make sure that I got memory and then I'll initialize the current head to null to signify that there is nothing in this list and just return that pointer that I got back from malloc. So now using this where everything is a pointer things actually look a bit better. So now in my insert front instead of having a pointer to a pointer, well I'll just take the link list by pointer itself. So this is still technically a pointer to a pointer, but it's easier to read because we're only reading one pointer at a single time. So we have a pointer to the link list and then the link list itself has a pointer to the node which might be better than a pointer to a pointer up to you. So but this is generally how people define link list. So if I want to rewrite my insert front I could take the link list by pointer and then if I change the value of its head it's changed for whoever called me as well because it is taken by a pointer. So here I'll do the same steps. I'll do the create node and then set the new nodes next to the link list head. So that apparently this will also do the dereference because I'm using this line but eventually you start to forget about this actually being dereferencing you just think about as accessing a field through a pointer but really it's doing the dereferencing and then we can go ahead and update the link list head to be equal to the node and then return the node. So now in main we can properly clean things up to so we can create a link list then we can insert front to the link list which is a lot more readable the value for print the link list which didn't really change and then I could go ahead and free them both. So any questions about that code? So it should do the same thing if everything works as I expect it to. It should just work. List just gives me four like I expect right and also if I go ahead let's say we create a new node called N3 and we are sure to free it as well this should also work right I hopefully will see that my list has three then a four. Any questions about that? Like I said lots and lots and lots and lots and lots of pointers. So our first attempt didn't work because C is called by value we went over that again these are just in the slide so you have them and head like I said head actually represents a link list so we should make a struct for it. Again just in the side so you have it that's us making that link list struct and creating that little helper function just so we can allocate it dynamically without having to call malloc ourselves every single time and initializing everything. So we rewrote insert front to take a link list instead it seemed to work we technically had to rewrite our print list to but the only difference was this one we didn't modify head or anything so it didn't actually matter but to make it match we can take it by a link list pointer and just get set current equals to the current head of the link list and the rest of it is just as before. So this was everything together and how we create our link list step by step is after create link list we just had a head pointing to null right and then when we inserted front well we had three steps right we created the node with the value four and its next was initially pointing to null so in order to make all the pointers drive we set the current head equal to the node with the value four we changed the value of next first but we changed it from null to null so doesn't really show up on the slide so after that we added another node it worked that was a bit more complicated so here's how that works step by step create a link list head pointed to null which says that there's nothing currently in the list then we created the node with the value four set its next equal to the current head which is null so null gets set to null who cares then we update the head to point to that new node and now whenever we added the node with the value three well we did the same three steps again right if this was an array we would have to move everything to the back so with this and inserting it to the front same three steps every time so we created a new node with the value three and then we set its next equal to the current head so that pointer points to the same head so the new nodes next so three next now points to four and then we just all we had to do is update the pointer of head just to point to three and then this is our new link list at the end so three is at the beginning and then after three comes four and then after four comes nothing we're done so let's see how bad things can go so I will need help with this so here here's a piazza post so I just flip those two lines together I I have now post on piazza help me my program does not work so help me what do you think is going to happen if I do this yeah so if I look at this too lot if I even just argue about adding a single node so I'll create a node and then alright I get the pointer back to the node that's the same as before then I set the link list head to point to that node so that's what we had before that's the starting node that should be at the front now that seems okay so now the head of the list is pointing to let's just say we created four first so it's pointing to four and then we set four is next equal to head which we just updated to four so its next is now four so the start of the list is four and what is after four four does that sound good no sounds like bad so let's say I just had four so if I just insert four with those two lines flipped around and I try to print the list what am I going to see is if after four is four yeah it should be look like an infant loop right so if I run was it called list insert three three three three three three three because well in this case I whoops in this case I didn't compile it whoops oh it has an error what is my error freed here alright you got recompiled so my infant loop is four goes back to four over and over and over and over and over again and looks the same if I go ahead and I just add three well what happens in that case is if the lines are flipped around then I just added the node three so the list head currently points to three and then three points to itself so if I go ahead and compile this now I just get three over no oops if I compile it again I'm having trouble compiling the I just get three over and over and over and over again and I will never see four so what is that so real quick there's me swapping the line of code here's me adding to the list so this was an infant loop and again just so you have it what was happen visually is whatever I tried to add my two nodes to the link and I had the values flipped or the assignment statements flipped I created a node with the value four its next was pointing to null and then well first I updated head to point to four and then I set next equal to the list head so it was then pointing to itself so it looked like this and then when I added node three well same steps happen again so I created a new node with the value three I updated head to point to it and then I updated its next pointer to three so now there's another thing we can notice where through the head pointer there is now no path for me to get to four so I have lost it forever and if I have lost it forever that is essentially we have a very bad memory leak because I can't call free on it anymore right I can't access its address anymore I lost it so I will never be able to free that memory and again this is like why Google Chrome if you make this error and you just lose some memory and you can't free it you can't free it forever it's just gonna waste space forever and if you do that a few thousand times you get Google Chrome and all the web browsers that waste like gigabytes of memory because well essentially boils down to that same thing so summary and again ask questions later on discord or whatever so because link lists are the one of the hardest topics we have so all you need to remember a link list is a sequence of nodes to represent a link list we just need to keep track of the first node and then each node keeps track of whatever the next node is on the list it does allow us some more flexibility over arrays in certain circumstances when to use which comes with a bit of experience but is good to know both the only kind of drawback is we have to update pointers and really understand pointers in order to understand link list but because we can just go ahead and update a few pointers well we don't have to change like where they are in memory so like we change the values in the array don't have to do that anymore all we have to do is update pointers so just remember pulling for you we're all in this together