 Hello everyone. So this is our final linked list-oriented video, and we are going to wrap up by talking about the doubly linked list. As if the linked list wasn't fun enough, there's an improvement on the linked list, and we will just talk about what that is in this video. Okay, so the doubly linked list data structure. What is it? Well, it's the same thing as a linked list, except that each node in the list has a reference to the node after it. That's next, that was already there, and the node before it, and that's the previous node. Okay, so our original node class was just the data and next. Now we've got data, next, and the previous node. Okay, so the downside to that is, well, you know, now whenever we add stuff and remove stuff from our list, we have to think about, are we, do we need to, how do we manage the previous link in the node class? So that's some more work we have to worry about. The good news about it, though, is it does give us some really, some really powerful tools, which is one, and this is maybe most important for us in this class. Just calling pop on the, which removes the last element of the list is now big O of one, just like in an array list. So this effectively, if we use a doubly linked list as our list data structure, now we can add and remove from the front, and we can add and remove from the rear, the tail, in big O of one time. So that makes this a pretty powerful data structure. Okay, all right, so we can also easily traverse the list in both directions. We can go from the head to the tail. We can go from the tail to the head, we can walk forward or backward. Sometimes that would be useful. And we can easily reverse the list. You do often, you can run into scenarios where you need to just completely switch the order of things in a list. A doubly linked list lets you do that pretty easily. The logic in general for adding, appending, removing, searching, and popping, it's generally all the same. You just need to take care of managing nodes previous and next variables. Okay, but the general algorithms are the same. All right, let me get rid of my head here for a second. And we are going to visualize this just to make sure you've got it straight in your head. Again, it looks very similar to a regular linked list, except now we've got a, each node has the data that it keeps. You know, the string, the int, whatever the data item is, a reference to the next node, and a reference to the previous node. Okay, so when we create a list, right, this is exactly the linked list we showed before. When we were talking about doubly linked, or regular linked list, we've got a head and the head node has Paris in it. Paris refers to the next node, which is Alice. And you'll see here though that Alice has a previous element that refers back to Paris. Okay, Paris being the head of the node, or the head of the list, his previous is empty, his previous is none. Okay, so we've got these forward lengths in the next, just like the linked list. Paris is next is Alice, Alice is next is Bob, Bob's next is Charlie. Charlie's next is none, because he's the tail of the list. Nothing comes after him. But in Charlie, Charlie has a previous, which is Bob. Bob has a previous, which is Alice. And Alice has a previous, which is Paris. But Paris has no previous because he is the head of the list. Okay, so we have to manage these links, just like we do in a regular linked list. And now we've got to measure them, manage them in both directions. So a little extra headache, but it does make the data structure more powerful. When we go to implement this, actually, you know what, before we go talking about implementation, let's switch over and just kind of make sure we've got a general understanding of what happens when we add and remove from the list. So we've got our little worksheet here, this little worksheet is focused on the doubly linked list. And we're going to do the same operations that we did before with the regular linked list. But just kind of get you in the mindset of we've got to update links going in both directions. Okay, so let's walk through the steps on this worksheet. Okay, so step one, the doubly linked list, we make a new one, a new empty list. Okay, step one, we're making a new empty list. And the doubly linked list, just like the regular linked list, has a notion of a head of the list and a notion of a tail. And when we start out, both of these are initialized to none. There's nothing in the list. The doubly linked list is also going to have a length variable, just like our regular linked list. Okay, and the length starts out at zero. Okay, so step one, we've got a kind of an empty linked list here, nothing there. All right, step two, we are going to add Harry to the list. Let me get my fancy eraser paper towel here. Okay, all right. So now we add Harry to the list. Now the doubly linked list node has both a next, just like the regular linked list, it's got its data, and it's got previous. Okay, so the doubly linked list node is going to look like this. In both of these are empty, there's nothing there, right? Harry's the only one in the list, so there's no next because no one comes after Harry yet, and there's no previous because no one comes before Harry. We now have one item in the list, so I'll update the length, and we have to update the head and the tail, right? So the linked list, the doubly linked list class does have a head. Oh, well, there's only one element in the list, one node, that's Harry, so the head points to Harry's node, and the tail also points to Harry's node. So there is a property in the doubly linked list. Whenever there's one item, the head and the tail point to that same thing. Notice also, we kind of maintain our general properties of tail nodes. Tail.next is none, right? There's nothing coming after it. In the doubly linked list, head.previous is also none. Nothing comes before the head, alright? So, step three, we are going to add Hermione. Okay, so, please take a little more room on the whiteboard. I'm going to draw her down here. I've got my doubly linked list node. So, Hermione, I am adding her. I call list.add. Okay, where does add put the thing in the list? It puts it at the head in the first position. Okay, so Hermione becomes the new head of the list. Hermione is the new head of the list. Well, when we create Hermione, who comes before the head? Who is previous to the head? Nobody, right? So her previous value is none. Who is next in the list? Well, conceptually speaking, it's Harry, right? So, I've got to create a link here. Let me move this tail marker out of the way for a minute. I've got to create a link from Hermione's next to Harry, right? Now, here's the additional step. Harry needs to be updated so that his previous points to Hermione. This is effectively going to let us go forward and backward through our list, okay? So, it's a two-step process of updating. Now, Harry's previous, it used to be none. We've got to make it point back to Hermione, okay? So, Harry is still the tail, though, right? Harry is the rear of the list, so Harry is still the tail. Woo! Okay, so we've got our links here. Hermione's the head of the list. Because she's the head, her previous is none. Hermione's next is Harry. Harry's previous goes back to Hermione. And Harry, since he is the tail, his next is none, right? So, we're updating these references more. We're updating these pointers. And then the last thing we have to do is make sure that we update the length of the list. It's now two. Sorry for the atrocious handwriting. It's kind of hard to write on this wobbly board. Alright, step four. We are going to append Ron to the list. Okay, so first, let's draw our node for Ron. I'll put it over here. His new node has a previous and it has a next. The data value is Ron. Alright, we are appending. Appending inserts at the end of the list. Okay? So currently, Harry is the tail of the list, right? He's the tail. So, we need to kind of attach Ron right here, right? After Harry comes Ron. Alright, so let's do this. So, let's attach Harry's next to Ron. Okay? And Ron, we need to update his previous because something does come before Ron. It's Harry. Okay? So, Ron's previous goes back to Harry. Like that. What is Ron's next? What should the value of Ron's next be? Oh, it should be none. Because Ron got appended to the end of the list. He is the tail of the list. Nothing comes after the tail. There's no next after the tail. Okay? So, the final, we're going to update our tail to point to Ron. He's now the latest thing. Right. And we update the length. And there we go. Alright, so that was step four. Now, we are going to pop. Okay? List.pop, step five. So, in step five, pop is called without specifying an index. Right? There's no position in step five given. It's just calling list.pop. When you call pop with no parameter, the default behavior is to remove the last item in the list. Right? So, Ron, short lived in this list. He was appended to it and now he's getting popped out. Okay? And this is where we will actually see a big improvement in the performance of the link, of a doubly linked list over a linked list. If we wanted to remove the last item in a linked list, we had to start at the beginning with cur and preve and walk all the way down the list. Well, here, because of this previous variable in the tail, right, we can just walk back to find the previous node. So, we don't have to walk from the beginning to the end of the list. We start at the end and just walk back a step. And that will let us very quickly and big of one time chop off the tail of this list. All right. So, sorry, Ron. It's time to pop you off of this list. So, what do we do? We're going to remove the tail, right? Ron is no longer the tail and we get rid of Ron's node. How do we get rid of Ron's node? Well, the way you get rid of Ron's node is very easy. Nodes only live in memory as long as someone points to them, right? So, currently, Harry dot next is pointing to Ron. If we set Harry's next value to none, right? Harry's no longer pointing to Ron and we set the tail as Harry. Now, inside our doubly linked list class, there's nothing at all that remembers Ron's node. Sorry, Ron. Ron's gone because everybody forgot about him. It's kind of like Coco. You see Coco, Pixar? Remember me? If nobody remembers that that object is in memory, it goes away, right? It gets collected. Garbage collection. All right. I should update the length here. Our length was three. Let me try and write this just a little more legibly. Our length was three and now we've got two nodes in there. So, our length is two. All right. Step six, list dot pop zero. Okay. Pop zero. So, pop can take an argument, right? And in this case, I give it zero. So, this is saying remove the item at index zero. Well, what's index zero of this list? It is the head, Hermione, right? So, we've got to get rid of Hermione. How do we delete a node in a linked list? We need to get rid of any reference to that node and that effectively deletes it, okay? So, what we will have to do to get rid of Hermione, to get rid of zero. Now, popping a specific index, like that you got a zero, one, five, hundred, ninety-nine, whatever it is. When you're popping an index, you've got to walk down the list, just like you did in the singly linked list. So, the code is going to look, the walking code is going to look very similar. It's going to look the same, frankly. It just so happens that we're popping the head of the list, the item at index zero. So, we're not going to walk very far in our code. Alright, so we've got to get rid of her, right? How do we get rid of her? Well, we first, we know we're getting rid of her, so we'll set the head variable to point to Harry, okay? And then we set Harry's previous to be none. So, Harry's previous is none. He's the head of the list, his previous is none. He's also the tail of the list, so his next is none. Now, there is nothing pointing to Hermione. Hermione's node is still pointing to Harry, but you don't have to worry about that. Because no one is pointing to Hermione, no one has a reference to her, she's gone, okay? So, the Python garbage collector will come along and wipe her out. She will no longer be in memory. Okay, so poor Hermione is gone. Harry's left all alone again. Oh, we've got to update our length. Our length is now one. We've got one node in the list. And finally, we're on to step seven. Step seven, we are list up removing Harry. So, we're removing by value. Again, just like in removing your regular linked list, you have to perform a search. You have to walk through the list looking to see if the node's data value matches the value you are trying to return. Well, our list here is simple. It's only got one item, so we'll find it pretty quickly. And if it's not there, if the data item isn't there, we raise our value error. Just like we did before. The interface for the doubly linked list is exactly the same as the operations, the interface for a singly linked list. It's all different under the hood, right? How you use it will be exactly the same. You won't see any difference in the calls that you make, okay? All right, so we found Harry. We got to delete him. Now, this is a special case, right? This is a special case where you're going to have to look and see, all right. I'm actually deleting the head of the list. I'm also deleting the tail of the list, right? So, this is definitely a special case. You're going to have to check for it. Just like you do currently when you remove one item. There's only one item in the list and you remove that only item, okay? So, this is also a special case. I'm not going to talk about the code. Just looking at this, what needs to happen, right? Well, to get rid of Harry, I got to get rid of any reference to him. What's referring to Harry? The head does and the tail does. So, if I set the head to none and I set the tail to none, nobody's pointing to Harry. Nobody's referring to Harry. So, the poor Python garbage collector will come along and scoop up Harry. All right, and then finally, you know, we've got to remove, set the length of the list equal to zero because everybody's gone, okay? So, this is, you know, just visualizing the operation of a doubly linked list. Very similar to a linked list, except you've got to manage those previous elements in the node data type, all right? All right, let's get back into the slides and kind of wrap this all up. Okay, so as I mentioned, when you go to implement your doubly linked list, the node object will have a previous variable, but that's it, no change there. The doubly linked list is going to have a head and a tail, just as the singly linked list does, and it's going to have the same exact operations, but the code that you have to write inside these operations will change to deal with managing previous variable. All right, so you do have to implement a doubly linked list to get an A on assignment number six. So, how do you do it? Basically, you should start with your linked list, okay? If your linked list is working great, hopefully it will be, you can just copy the code basically, except, and then you're going to have to modify some things. What do you have to modify? You must update nodes previous and next variables in anything that adds and removes items from the list. So add, append, remove, pop, right? Generally, the algorithm will look the same, but you've got to deal with this previous variable, okay? The second thing you do is you have to make better use of the tail when pop with no parameter or no argument is called, okay? This is going to be an extra if statement in there. It says, hey, if this is none in here, let me pop, but I'm going to pop using the tail so that I don't have to walk the list. It's a special case, right? But that's it, right? I say that's it, at flippantly, right? This is actually a little bit of work. You've got to think about it. You've got to think about what you have to do to manage the previous variable in special cases where you're, like, deleting from the head node and the tail node, that sort of thing, all right? Just remembering to update both variables is challenging enough, but draw out some examples on a piece of paper of what needs to happen conceptually and then make it happen in the code, all right? Okay, so here's our big O for all of our list data structures. We've got the array list and the link list. This is the same as last time. And now we've got the doubly linked list in here. The only change over a linked list is that popping, removing the item at the end of the list, is now a big O of one. And that's a good thing. That is a big improvement for us, right? We've got adding at the front, at the head. We've got removing at the head. We've got adding at the tail. And we've got removing from the tail, all big O of one time. And that's kind of crucial because now we've got a doubly linked list that can, we're going, we want to use it. You know, when would we use, you know, a linked list implementation? Well, you would use a linked list implementation of a list when you are only, primarily only concerned with doing reads and writes from the ends, okay? Like busy queues or decks, okay? Decks are used in CPU process scheduling. That's busy. Like a lot of stuff is going on there. Queues, network traffic routing or busy, busy, busy. A linked list, probably good there. You choose array based implementations for everything else, right? And this is what you have used so far. You will see later that array based implementations are very helpful for searching and sorting data. We will talk about that in the future. So arrays are very powerful, but they are not the best. If what you really care about is a list of items where you're doing reading and writing of the data at just the ends. Okay, so that's it. Kind of wrapped up our linked lists there. And I will, why don't I make my face big for once, okay? So that's it for linked lists. Big, heady concept. Get started early on the homework. The homework is going to be in two parts this time. You'll have two weeks to do it, but please don't get behind on it. It's a bear that said, if you come out the other end of this linked list assignment with a pretty good grasp of what node.next means, what node.previous means, what cur gets cur.next does, you've taken a big leap in your understanding of how programs work. Okay, so feel good about that. Reach out to me if you have questions. This is challenging stuff. I am more than happy to help. And I will talk to you next time.