160 likes | 168 Views
Discover the importance of linked lists and why they are crucial in various applications, from video games to search engines. Learn the limitations of arrays and how linked lists offer a dynamic solution for storing an arbitrary number of items efficiently. Dive into a C++ scavenger hunt analogy to grasp the concept of linked lists as a data structure. Explore how linked lists enable fast insertion and deletion of items, avoiding the pitfalls of fixed-sized arrays. Take your programming skills to the next level with this insightful guide.
E N D
Linked Lists…Why should you care? Linked Lists are used in everything from video games to search engines. Any time you don’t know how many items you’ll need to store ahead of time, you use ‘em. And virtually every job interview will grill you on them. So pay attention!
Arrays are great… But… int main() { int array[100]; … } Arrays are great when you need to store a fixed number of items… But what if you don’t know how many items you’ll have ahead of time? int main() { intnumItems, *ptr; cin >> numItems; ptr = newint[numItems]; } int main() { // might have 10 items or 1M int array[1000000]; … } Still requires us to know the size ahead of time! Then you have to reserve enough slots for the largest possible case. What a waste of space! Even new/delete don’t really help! names[0] Andrew And what if you need to insert a new item in the middle of an array? names[1] Betty names[2] David It takes nearly 1M steps to add a new item! Carey names[3] Elaine We have to move every item below the insertion spot down by one! names[4] Frank … names[999998] Zappa And it’s just as slow if we wantto delete an item! Yuck! names[999999]
So Arrays Aren’t Always Great Hmm… Can we think of an approach from “real life” that works better than a fixed-sized array? What can we think of that: Using this approach, we can store an arbitrary number of items! The hunt starts with a clue to the location of the first chest… allows you to store an arbitrary number of items Then each chest holds an item and a clue to the location of the next chest. There’s no fixed limit to the number of chests and clues we can have! makes it fast to insert a new item in the middle How about organizing the items as we would in a Scavenger Hunt? makes it fast to deletean item from the middle Clue: The first item is by the tree ? Clue: The next item is by the tower Clue: The next item is by the house Clue: This is the last item!
So Arrays Aren’t Always Great Clue: Also, using this approach we can quickly add a new item to the middle! For instance, let’s add a new treasure between our bananas and our toilet paper. All we have to do is add a new chest and change a few clues! Clue: The first item is by the tree Clue: The next item is by the tower Clue: The next item is by the house Clue: The next item is by the temple First we copy the previous clue to our new chest. The next item is by the house Then we update the previous clue to point to our new chest! Clue: This is the last item!
So Arrays Aren’t Always Great Clue: The next item is by the house Finally, using this approach we can quickly remove an item from the middle! All we have to do is remove the target chest and change a single clue! Clue: The first item is by the tree ? For instance, let’s remove this chest from the hunt… Clue: The next item is by the tower Clue: The next item is by the temple The next item is by the tower Clue: This is the last item!
A C++ Scavenger Hunt? Ok, so in our Scavenger Hunt, we had: A clue that leads us to our first treasure chest. Each chest then holds an item(e.g., bananas) and a clue that leads us to the next chest. Clue: The first item is by the tree Clue: The next item is by the house So here’s the question… can we simulate a Scavenger Hunt witha C++ data structure? Why not? Let’s see how. Clue: The next item is by the tower.
A C++ Scavenger Hunt? structChest { }; Well, we can use a C++ struct to represent a Chest. string treasure; Chest * nextChest; As we know, each Chest holds two things: A treasure – let’s use a string variable to hold our treasure, e.g., “plunger”. This line basically says that each Chest variable holds a pointer… The location of the next chest – let’s represent that with a pointer variable. Clue: The first item is by the tree to another Chest variable. We can now define a Chest variable for each of the items in our scavenger hunt! Clue: The next item is by the house Clue: The next item is by the tower.
A C++ Scavenger Hunt? first 5000 treasure treasure “bananas” “TP” nextChest nextChest 3400 1200 structChest { }; Well, we can use a C++ struct to represent a Chest. string treasure; Chest * nextChest; As we know, each Chest holds two things: A treasure – let’s use a string variable to hold our treasure, e.g., “plunger”. OK, let’s see the C++ version of a simplified scavenger hunt data structure! Clue: The first item is by the tree The location of the next chest – let’s represent that with a pointer variable. We can now define a Chest variable for each of the items in our scavenger hunt! Clue: The next item is by the house And we can define a pointer to point to the very first chest – our first clue! Chest *first; // pointer to our 1st chest Clue: The next item is by the tower.
A C++ Scavenger Hunt? first chest3 chest2 chest1 treasure treasure treasure nextChest nextChest nextChest We want to link up our first chest to the second chest… This data structure is called a “linked list.” And update our first chest so it points to it… struct Chest { string treasure; }; …should hold the address of the first chest! Why? Because each element in the list is ”linked” by a pointer to the next element. int main(void) { Chest *first; Chest chest1, chest2, chest3; first = &chest1; chest1.treasure = “toast"; chest1.nextChest = &chest2; chest2.treasure = “bacon"; chest2.nextChest = &chest3; chest3.treasure = “eggs"; } 1000 1000 Chest * nextChest; “toast" We call each item in the linked list a “node.” Finally, we’ll indicate that the third chest is the last in the scavenger hunt.We do this by setting its nextChestpointer to nullptr. nullptris a special C++ constant that means “invalid pointer value.” 1020 1020 “bacon" Our first pointer … 1040 1040 “eggs" chest3.nextChest = nullptr; So we’ll get the address of the second chest… nullptr
Linked Lists second third first treasure treasure treasure nextChest nextChest nextChest struct Chest { string treasure; }; Normally, we don’t use local variables to create our linked list. “Hey OS, can you allocate 20 bytes for me?” int main(void) { Chest *first; Chest chest1, chest2, chest3; first = &chest1; chest1.treasure = “toast"; chest1.nextChest = &chest2; chest2.treasure = “bacon"; chest2.nextChest = &chest3; chest3.treasure = “eggs"; } “Hey OS, can you allocate 20 bytes for me?” Chest * nextChest; Instead we use dynamically-allocated variables (and pointers!). “Hey OS, can you allocate 20 bytes for me?” Chest *first, *second, *third; first = new Chest; second = new Chest; 2200 5000 3700 third = new Chest; 5000 2200 3700 OS: “Sure – I’ve reserved some memory for you at location 2200.” OS: “Sure – I’ve reserved some memory for you at location 5000.” OS: “Sure – I’ve reserved some memory for you at location 3700.” chest3.nextChest = nullptr;
Linked Lists The pointer to the top item in the linked list is traditionally called the “head pointer.” second third first treasure treasure treasure nextChest nextChest nextChest struct Chest { string treasure; }; OK, now let’s add cargo and link ‘em up! Given just the head pointer, you can reach every element in the list… without using your other external pointers! Again, in our last node, we’ll set its nextChestpointer to nullptr. This indicates that it’s the last item in the list. int main(void) { } Chest * nextChest; head head head head head Chest *first, *second, *third; first = new Chest; second = new Chest; 3700 2200 5000 "toast" treasure third = new Chest; 5000 nextChest treasure first -> first->treasure = "toast"; nextChest first->nextChest = second; second first -> 2200 2200 second->treasure = "bacon"; "bacon" Oh – and let’s not forget to free our treasure chests when we’re done with them! second->nextChest = third; 3700 3700 third->treasure = "eggs"; When we encounter a nextChestpointer whose value is nullptr, this indicates we’re at the end. third->nextChest = nullptr; "eggs" delete head; delete second; delete third; nullptr
Linked Lists structNode // student node { intstudentID; string name; intphoneNumber; float gpa; Node *next; }; structNode { string value; }; structNode { string value; }; structNode { string treasure; }; struct Chest { string treasure; }; Ok, it’s time to start using the right Computer Science terms. int main(void) { } int main(void) { } int main(void) { } int main(void) { } Node * next; Node * nextChest; Node * nextChest; Chest * nextChest; Instead of calling them “chests", let’s call each item in the linked list a “Node”. Node *head, *second, *third; Node *head, *second, *third; Node *head, *second, *third; Chest *head, *second, *third; head = new Node; head = new Node; head = new Chest; head = new Node; second = new Node; second = new Node; second = new Node; second = new Chest; And instead of calling the value held in a node treasure, let’s call it “value”. third = new Node; third = new Node; third = new Chest; third = new Node; head->treasure = "toast"; head->value = "toast"; head->value = "toast"; head->treasure = "toast"; head->nextChest = second; head->nextChest = second; head->nextChest = second; head->next= second; second->value = "bacon"; second->value = "bacon"; second->treasure = "bacon"; second->treasure = "bacon"; And, instead of calling the linking pointer nextChest, let’s call it “next”. second->nextChest = third; second->next = third; second->nextChest = third; second->nextChest = third; third->value = "eggs"; third->treasure = "eggs"; third->value = "eggs"; third->treasure = "eggs"; third->nextChest = nullptr; third->nextChest = nullptr; third->nextChest = nullptr; third->next = nullptr; delete head; delete second; delete third; delete head; delete second; delete third; delete head; delete second; delete third; Finally, there’s no reason a Node only needs to hold a single value! delete head; delete second; delete third;
Linked Lists Note: The delete command doesn’t kill the pointer… r q p value value next next it kills what the pointer points to! Before we continue, here’s a short recap on what we’ve learned: 8000 structNode { string value; }; blah To allocate new nodes: Node *p = new Node; Node *q = new Node; 4000 int main(void) { } Node * next; To link node p… Node *head, *second, *third; 4000 4000 head = new Node; To change/access a node p’s value: p->value = “blah”; to node q. second = new Node; 4000 8000 “blah” third = new Node; cout << p->value; head->value = "toast"; To make node p link to another node that’s at address q: p->next = q; head->next= second; second->value = "bacon"; To free your nodes: delete p; delete q; second->next = third; third->value = "eggs"; To get the address of the node after p: Node *r = p->next; third->next = nullptr; delete head; delete second; delete third; To make node q a “terminal” node: q->next = nullptr; nullptr
Linked Lists structNode { string value; }; Normally, we don’t create our linked list all at once in a single function. We normally don’t create our linked list all at once like this. int main(void) { } Node * next; After all, some linked lists hold millions of items! That wouldn’t fit! Node *head, *second, *third; head = new Node; second = new Node; Instead, we create a dedicated class (an ADT) to hold our linked list… third = new Node; head->value = "toast"; head->next= second; And then add a bunch of member functions to add new items (one at a time), process the items, delete items, etc. second->value = "bacon"; second->next = third; third->value = "eggs"; third->next = nullptr; OK, so let’s see our new class. delete head; delete second; delete third;
A Linked List Class! value value value next next next First, let’s shrink our Node definition font a bit to make room for our new class! struct Node { string value; }; In the simplest type of linked list class, the onlymember variable we need is a head pointer. class LinkedList { public: private: }; int main(void) { } Node *next; Why? Given just the head pointer, we can follow the links to every node in the list. Node *head, *second, *third; head = new Node; And since we can find all the nodes, we can also link in new ones, delete them, etc.. head 5000 second = new Node; 5000 3700 2200 "toast" third = new Node; 5000 2200 head head->value = "toast"; head->next= second; This is all our class needs to hold! Ok, so let’s add a head pointer to our class. second->value = "bacon"; "bacon" second->next = third; third->value = "eggs"; third->next = nullptr; 3700 "eggs" delete head; delete second; delete third; nullptr Node *head;