Presentation is loading. Please wait.

Presentation is loading. Please wait.

1 Lecture #4 Resource Management, Part 2 –Assignment Operators Basic Linked Lists –Insertion, deletion, destruction, traversals, etc. Advanced Linked.

Similar presentations


Presentation on theme: "1 Lecture #4 Resource Management, Part 2 –Assignment Operators Basic Linked Lists –Insertion, deletion, destruction, traversals, etc. Advanced Linked."— Presentation transcript:

1

2 1 Lecture #4 Resource Management, Part 2 –Assignment Operators Basic Linked Lists –Insertion, deletion, destruction, traversals, etc. Advanced Linked Lists –Tail Pointers –Doubly-linked Lists

3 2 The Assignment Operator Last time we learned how to construct a new class variable using the value of an existing variable. int main() { Circ foo(1,2,3); Circ bar(4,5,6); bar = foo; } Now lets learn how to set the value of an existing variable to the value of an another existing variable. In this example, both foo and bar have been constructed. Both have had their member variables initialized. Then we set bar equal to foo. int main() { Circ x(1,2,3); Circ y = x; }

4 3 The Assignment Operator int main() { Circ foo(1,2,3); Circ bar(4,5,6); bar = foo; } In this case, the copy constructor is NOT used to copy values from foo to bar. Instead, a special member function called an assignment operator is used. If you don’t define your own assignment operator… Then C++ provides a default version that just copies each of the members. foo m_x m_y m_rad 123123 bar m_x m_y m_rad 456456 1 2 3 Lets see how to define our own assignment operator. Why isn’t bar’s copy constructor called? Because bar was already constructed on the line above! The variable already exists and is already initialized!

5 4 class Circ { public: Circ(float x, float y, float r) { m_x = x; m_y = y; m_rad = r; } float GetArea(void) { return(3.14159*m_rad*m_rad); } private: float m_x, m_y, m_rad; }; void setMeEqualTo(const Circ &src) { m_x = src.m_x; m_y = src.m_y; m_rad = src.m_rad; } The Assignment Operator The syntax for an assignment operator is a bit confusing. So lets define a simpler version first… Here’s how we’d use our new function. int main() { Circ foo(1,2,3); Circ bar(4,5,6); bar.setMeEqualTo(foo); } // same as bar = foo; bar Hmmm.. This looks familiar, doesn’t it? What does it remind you of? class Circ {... void setMeEqualTo(const Circ &src) { m_x = src.m_x; m_y = src.m_y; m_rad = src.m_rad; }... private: m_x m_y m_rad } class Circ {... void setMeEqualTo(const Circ &src) { m_x = src.m_x; m_y = src.m_y; m_rad = src.m_rad; }... private: m_x m_y m_rad } 1 2 3 1 5 2 3 When we’re done, bar is a perfect clone of foo! foo 4 6

6 5 class Circ { public: Circ(float x, float y, float r) { m_x = x; m_y = y; m_rad = r; } float GetArea(void) { return(3.14159*m_rad*m_rad); } private: float m_x, m_y, m_rad; }; The Assignment Operator void setMeEqualTo { m_x = src.m_x; m_y = src.m_y; m_rad = src.m_rad; } operator= 1.The function name is operator= Now lets see what a real assignment operator looks like. 2. The function return type is a reference to the class. Circ & 3. The function returns *this when its done. return(*this); The const keyword guarantees that the source object (src) is not modified during the copy. You MUST pass a reference to the source object. This means you have to have the & here!!! (const Circ &src)

7 6 The Assignment Operator int main() { Circ foo(1,2,3); Circ bar(4,5,6); bar = foo; } class Circ { public: Circ(float x, float y, float r) { m_x = x; m_y = y; m_rad = r; } float GetArea(void) { return(3.14159*m_rad*m_rad); } private: float m_x, m_y, m_rad; }; void Assign (const Circ &src) { m_x = src.m_x; m_y = src.m_y; m_rad = src.m_rad; } operator=Circ & return(*this); class Circ {... Circ &operator=(const Circ &src) { m_x = src.m_x; m_y = src.m_y; m_rad = src.m_rad; return(*this); }... private: m_x m_y m_rad } foo class Circ {... Circ &operator=(const Circ &src) { m_x = src.m_x; m_y = src.m_y; m_rad = src.m_rad; return(*this); }... private: m_x m_y m_rad } bar 4 1 2 3 1 6 5 2 3 C++: “Ooh, the programmer is using the equal sign to set bar equal to foo.” C++: “Since the programmer defined an assignment operator for Circles, I’ll use it to do the assignment.” bar.operator=(foo); Another way to read: bar = foo; is: bar.operator=(foo); i.e., we’re calling bar’s operator= member function! So, to summarize… If you’ve defined an operator= function in a class… Then any time you use the equal sign to set an existing variable equal to another… C++ will call the operator= function of your target variable and pass in the source variable!

8 7 class Squares { public: Squares(int n) { m_n = n; m_sq = new int[n]; for (int j=0;j<n;j++) m_sq[j] = (j+1)*(j+1); } ~ Squares(){delete []m_sq;} void printSquares() { for (int j=0;j<n;j++) cout << m_sq[j] << endl; } private: int *m_sq, m_n; }; The Assignment Operator Remember our updated Squares class… Ok – so when would we ever need to write our own Assignment Operator? After all, C++ copies all of the fields for us automatically if we don’t write our own! Lets see what happens if we use the default assignment operator with it…

9 8 class Squares { public: Squares(int n) { m_n = n; m_sq = new int[n]; for (int j=0;j<n;j++) m_sq[j] = (j+1)*(j+1); } ~ Squares(){delete []m_sq;} void printSquares() { for (int j=0;j<n;j++) cout << m_sq[j] << endl; } private: int *m_sq, m_n; }; The Assignment Operator int main() { Squares a(3); Squares b(4); b = a; } a m_n m_sq 3 00000800 00000804 00000808 800 1 4 9 b m_n m_sq 4 900 00000900 00000904 00000908 00000912 1 4 9 16 3 800 // a’s d’tor is called, then b’s Operating System, can you free the memory at address 800 for me. No sweat, homie. Consider it freed. Operating System, can you free the memory at address 800 for me. You already asked me to do that! I can’t free it twice!!! ERROR!!!! Oh – and by the way, you never freed the memory at 900, you know! First a’s destructor is called. Then b’s destructor is called.

10 9 class Squares { public: Squares(int n) { m_n = n; m_sq = new int[n]; for (int j=0;j<n;j++) m_sq[j] = (j+1)*(j+1); } ~ Squares(){delete []m_sq;} void printSquares() { for (int j=0;j<n;j++) cout << m_sq[j] << endl; } private: int *m_sq, m_n; }; The Assignment Operator int main() { Squares a(3); Squares b(4); b = a; } a m_n m_sq 3 00000800 00000804 00000808 800 1 4 9 b m_n m_sq 4 900 00000900 00000904 00000908 00000912 1 4 9 16 3 800 // a’s d’tor is called, then b’s When we copy a’s members into b, we have 2 problems: 1.Our b variable forgets where it reserved its memory. 2.Both a and b point to the same memory. When a is destructed, it frees the memory at 800. But this memory is still being referred to by b! Utoh! Finally, when b is destructed, it tries to free the memory at 800 – AGAIN! But this memory was already freed by a! And, b forgot where its original memory was, so it forgets to free that too! ?

11 10 The Assignment Operator The assignment operator must: 1.Free all dynamic memory used by the target instance. 2.Re-allocate memory in the target instance to hold any member variables from the source instance. 3.Explicitly copy the contents of the source instance to the target instance. Any time your class: A.Allocates dynamic memory B.Opens system resources (like opening a file) You need to define your own assignment operator

12 11 The Assignment Operator General assignment operator syntax: class SomeClass { public: SomeClass & operator=(const SomeClass &src) { // 1. Free all memory in the target instance // 2. Reallocate memory for the target instance // 3. Copy data from src into the target instance // 4. Return a reference to the target instance } }; Don’t forget the const keyword! Don’t forget the & symbol to make src a reference!

13 12 The Assignment Operator class Squares { public: Squares(int n) { … } ~ Squares(){ delete[]m_sq; } // assignment operator: Squares &operator=(const Squares &src) { delete [] m_sq; m_n = src.m_n; m_sq = new int[m_n]; for (int j=0;j<m_n;j++) m_sq[j] = src.m_sq[j]; return(*this); } void printSquares() {... } private: int *m_sq, m_n; }; int main() { Squares a(3); Squares b(4); b = a; } a m_n m_sq 3 000800 000804 000808 800 1 4 9 b m_n m_sq 4 900 000900 000904 000908 000912 1 4 9 16 src Operating System, I no longer need the memory at location 900. Ok, I’ll free that up for someone else to use. 3 OS: Can you reserve 12 bytes of memory for me? Sure. Here’s 12 bytes of memory for you at address 860. 000860 000864 000868 860 1 4 9

14 13 The Assignment Operator class Squares { public: Squares(int n) { … } ~ Squares(){ delete[]m_sq; } // assignment operator: Squares &operator=(const Squares &src) { delete [] m_sq; m_n = src.m_n; m_sq = new int[m_n]; for (int j=0;j<m_n;j++) m_sq[j] = src.m_sq[j]; return(*this); } void printSquares() {... } private: int *m_sq, m_n; }; int main() { Squares a(3); Squares b(4); b = a; } a m_n m_sq 3 000800 000804 000808 800 1 4 9 b m_n m_sq 4 900 3 000860 000864 000868 860 1 4 9 // a’s d’tor is called, then b’s Operating System, I no longer need the memory at location 800. Ok, I’ll free that up for someone else to use. Operating System, you can free the memory at address 860. Ok, I’ll free that for you. … and everything is freed perfectly!

15 14 The Assignment Operator class CSNerd { public:... CSNerd &operator=(const CSNerd &src) { m_numPCs = src.m_numPCs; m_hasMac = src.m_hasMac; return(*this); } private: int m_numPCs; bool m_hasMac; }; Answer: So we can do multiple assignments in the same statement, like this… int main() { CSNerd sam(5,false); CSNerd ted(10,false); CSNerd tim(3,true); tim = ted = sam; } Question: Why do we have return(*this) at the end of the assignment operator function? sam class CSNerd { CSNerd &operator= (const CSNerd &src) { m_numPCs = src.m_numPCs; m_hasMac = src.m_hasMac; return(*this); } m_numPCs m_hasMac }; ted class CSNerd { CSNerd &operator= (const CSNerd &src) { m_numPCs = src.m_numPCs; m_hasMac = src.m_hasMac; return(*this); } m_numPCs m_hasMac }; tim class CSNerd { CSNerd &operator= (const CSNerd &src) { m_numPCs = src.m_numPCs; m_hasMac = src.m_hasMac; return(*this); } m_numPCs m_hasMac }; All assignment is performed right-to-left… First we call ted’s assignment operator to assign him to sam. 5 false 10 false 3 true 5 false “this” is a special C++ pointer variable that holds the address of the current object (i.e., ted’s address in RAM) So if “this” is a pointer to ted, then “*this” refers to the whole ted variable. So this line returns the ted variable itself! Strange huh? A member function of a variable can return the variable itself!?!? So the statement: “ted = sam” is just replaced by the ted variable! ted Next, C++ sets tim equal to ted. 5false And this line returns the tim variable, so if we wanted, we could do yet another assignment! tim So, to sum up… The assignment operator returns “*this” so that there’s always a variable on the right hand side of the = for the next assignment. bill =

16 15 The Assignment Operator Our assignment operator has one problem with it… Can anyone guess what it is? class Squares { public:... Squares &operator=(const Squares &src) { delete [] m_sq; m_n = src.m_n; m_sq = new int[m_n]; for (int j=0;j<m_n;j++) m_sq[j] = src.m_sq[j]; return(*this); } private: int *m_sq, m_n; }; void f(Squares &x,Squares &y) {... x = y; } int main() { Squares a(3); f(a,a); } a m_n m_sq 3 000800 000804 000808 800 1 4 9 Hmm... What happens if we set a to itself? src Operating System, you can free the memory at address 800. Ok, I’ll free that up for someone else to use. 3 OS: Can you reserve 12 bytes of memory for me? Sure. Here’s 12 bytes of memory for you at address 420. 000420 000424 000428 So what values are at location 420-428?? RANDOM ones! 420 -52 19 34 So now we copy the random values over themselves! // really a = a; !!! “Aliasing” is when we use two different references/pointers to refer to the same variable. It can cause unintended problems!

17 16 The Assignment Operator The fix: Our assignment operator function can check to see if a variable is being assigned to itself, and if so, do nothing… class Squares {... Squares &operator=(const Squares &src) { delete [] m_sq; m_n = src.m_n; m_sq = new int[m_n]; for (int j=0;j<m_n;j++) m_sq[j] = src.m_sq[j]; return(*this); } … }; And we’re done! if (&src == this) return(*this); // do nothing

18 17 Copy Constructor/ Assignment Review Question: which of the following use the copy constructor and which use the assignment operator? int main()// #1 { Squaresa(4), b(3); b = a; } int main()// #2 { Squares c(5), d(c); Squares e = d; } // #3 Squares func(void) { Squares g(15); return(g); } int main() { Squares f = func(); }

19 18 Challenge Write an assignment operator for our CSNerd Class: struct Book { string title; string author; }; class CSNerd { public: CSNerd(string name) { m_myBook = nullptr; m_myName = name; } void giveBook(string t, string a) { m_myBook = new Book; m_myBook->title = t; m_myBook->author = a; } ~CSNerd() { delete m_myBook; } private: Book *m_myBook; string m_myName; };

20 19 Linked Lists

21 Arrays are great… But… 20 Arrays are great when you need to store a fixed number of items… int main() { int array[100]; … } But what if you don’t know how many items you’ll have ahead of time? int main() { // might have 10 items or 1M int array[1000000]; … } Then you have to reserve enough slots for the largest possible case. What a waste of space! And what if you need to insert a new item in the middle of an array? Andrew Betty David Elaine Frank Zappa … names[0] names[1] names[2] names[3] names[4] names[999998] names[999999] Carey We have to move every item below the insertion spot down by one! And it’s just as slow if we want to delete an item! Yuck! int main() { int numItems, *ptr; cin >> numItems; ptr = new int[numItems]; } Even new/delete don’t really help! Still requires us to know the size ahead of time! It takes nearly 1M steps to add a new item!

22 So Arrays Aren’t Always Great 21 Hmm… Can we think of an approach from “real life” that works better than a fixed-sized array? What can we think of that: allows you to store an arbitrary number of items makes it fast to insert a new item in the middle makes it fast to delete an item from the middle How about organizing the items as we would in a Scavenger Hunt? ? Clue: The first item is by the tree Clue: The next item is by the house Clue: The next item is by the tower Clue: This is the last item! Using this approach we can store an arbitrary number of items! There’s no fixed limit to the number of chests and clues we can have! The hunt starts with a clue to the location of the first chest… Then each chest holds an item and a clue to the location of the next chest.

23 So Arrays Aren’t Always Great 22 Clue: The first item is by the tree Clue: The next item is by the house Clue: The next item is by the tower Clue: This is the last item! Also, using this approach we can quickly add a new item to the middle! All we have to do is add a new chest and change a few clues! For instance, let’s add a new treasure between our books and our shell. Clue: Clue: The next item is by the temple Then we update the previous clue to point to our new chest! First we copy the previous clue to our new chest. The next item is by the house

24 So Arrays Aren’t Always Great 23 ? Clue: The first item is by the tree Clue: The next item is by the temple Clue: The next item is by the tower Clue: This is the last item! 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 next item is by the house The next item is by the tower For instance, let’s remove this chest from the hunt…

25 24 Ok, so in our Scavenger Hunt, we had: So here’s the question… can we simulate a Scavenger Hunt with a C++ data structure? A C++ Scavenger Hunt? A clue that leads us to our first treasure chest. Each chest then holds an item (e.g., books) 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 Why not? Let’s see how. Clue: The next item is by the tower.

26 struct Chest { }; 25 Well, we can use a C++ struct to represent a Chest. As we know, each Chest holds two things: A treasure – let’s use a string variable to hold our treasure, e.g., “shells”. string treasure; The location of the next chest – let’s represent that with a pointer variable. Chest * nextChest; A C++ Scavenger Hunt? We can now define a Chest variable for each of the items in our scavenger hunt! Clue: The first item is by the tree Clue: The next item is by the house Clue: The next item is by the tower. This line basically says that each Chest variable holds a pointer… to another Chest variable.

27 treasure “books” nextChest 3400 Clue: The next item is by the tower. struct Chest { }; 26 Well, we can use a C++ struct to represent a Chest. Chest *first; // pointer to our 1 st chest As we know, each Chest holds two things: A treasure – let’s use a string variable to hold our treasure, e.g., “shells”. string treasure; The location of the next chest – let’s represent that with a pointer variable. Chest * nextChest; And we can define a pointer to point to the very first chest – our first clue! A C++ Scavenger Hunt? Clue: The first item is by the tree Clue: The next item is by the house treasure “shells” nextChest 1200 first 5000 We can now define a Chest variable for each of the items in our scavenger hunt! OK, let’s see the C++ version of a simplified scavenger hunt data structure!

28 A C++ Scavenger Hunt? struct Chest { string treasure; }; Chest * nextChest; int main(void) { Chest *first; Chest chest1, chest2, chest3; first = &chest1; chest1.treasure = “books"; chest1.nextChest = &chest2; chest2.treasure = “shells"; chest2.nextChest = &chest3; chest3.treasure = “cash"; } chest1 treasure nextChest chest2 treasure nextChest “books" 1000 1020 chest3 treasure nextChest 1040 27 “shells" 1040 “cash" We want to link up our first chest to the second chest… So we’ll get the address of the second chest… first 1000 And update our first chest so it points to it… This data structure is called a “linked list.” nullptr chest3.nextChest = nullptr; Finally, we’ll indicate that the third chest is the last in the scavenger hunt. We do this by setting its nextChest pointer to nullptr. nullptr is a special C++ constant that means “invalid pointer value.” We call each item in the linked list a “node.” …should hold the address of the first chest! Our first pointer … Why? Because each element in the list is ”linked” by a pointer to the next element.

29 28 Normally, we don’t use local variables to create our linked list. Linked Lists int main(void) { Chest *first; Chest chest1, chest2, chest3; first = &chest1; chest1.treasure = “books"; chest1.nextChest = &chest2; chest2.treasure = “shells"; chest2.nextChest = &chest3; chest3.treasure = “cash"; } chest3.nextChest = nullptr; struct Chest { string treasure; }; Chest * nextChest; Instead we use dynamically- allocated variables (and pointers!). Chest *first, *second, *third; first = new Chest; first second third “Hey OS, can you allocate 20 bytes for me?” OS: “Sure – I’ve reserved some memory for you at location 5000.” treasure nextChest 5000 treasure nextChest 2200 5000 second = new Chest; “Hey OS, can you allocate 20 bytes for me?” OS: “Sure – I’ve reserved some memory for you at location 2200.” 2200 third = new Chest; treasure nextChest 3700 “Hey OS, can you allocate 20 bytes for me?” OS: “Sure – I’ve reserved some memory for you at location 3700.” 3700

30 29 Linked Lists int main(void) { } struct Chest { string treasure; }; Chest * nextChest; Chest *first, *second, *third; first = new Chest; first second third treasure nextChest 5000 treasure nextChest 2200 5000 second = new Chest; 2200 third = new Chest; treasure nextChest 3700 OK, now let’s add cargo and link ‘em up! first->treasure = "books"; first->nextChest = second; "books" 2200 second->treasure = "shells"; second->nextChest = third; "shells" 3700 third->treasure = "cash"; "cash" third->nextChest = nullptr; nullptr head The pointer to the top item in the linked list is traditionally called the “head pointer.” head 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 nextChest pointer to nullptr. This indicates that it’s the last item in the list. treasure first -> treasure nextChest first -> nextChest second delete head; delete second; delete third; Oh – and let’s not forget to free our treasure chests when we’re done with them! When we encounter a nextChest pointer whose value is nullptr, this indicates we’re at the end.

31 struct Chest { string treasure; }; Chest * nextChest; Chest *head, *second, *third; head = new Chest; second = new Chest; third = new Chest; head->treasure = "books"; head->nextChest = second; second->treasure = "shells"; second->nextChest = third; third->treasure = "cash"; third->nextChest = nullptr; int main(void) { } delete head; delete second; delete third; Instead of calling them “chests", let’s call each item in the linked list a “Node”. 30 Linked Lists Ok, it’s time to start using the right Computer Science terms. And instead of calling the value held in a node treasure, let’s call it “value”. And, instead of calling the linking pointer nextChest, let’s call it “next”. struct Node { string treasure; }; Node * nextChest; Node *head, *second, *third; head = new Node; second = new Node; third = new Node; head->treasure = "books"; head->nextChest = second; second->treasure = "shells"; second->nextChest = third; third->treasure = "cash"; third->nextChest = nullptr; int main(void) { } delete head; delete second; delete third; struct Node { string value; }; Node * nextChest; Node *head, *second, *third; head = new Node; second = new Node; third = new Node; head->value = "books"; head->nextChest = second; second->value = "shells"; second->nextChest = third; third->value = "cash"; third->nextChest = nullptr; int main(void) { } delete head; delete second; delete third; struct Node { string value; }; Node * next; Node *head, *second, *third; head = new Node; second = new Node; third = new Node; head->value = "books"; head->next = second; second->value = "shells"; second->next = third; third->value = "cash"; third->next = nullptr; int main(void) { } delete head; delete second; delete third; Finally, there’s no reason a Node only needs to hold a single value! struct Node // student node { int studentID; string name; int phoneNumber; float gpa; Node *next; };

32 31 Linked Lists Before we continue, here’s a short recap on what we’ve learned: To allocate new nodes: Node *p = new Node; Node *q = new Node; To change/access a node p’s value: p->value = “blah”; To make node p link to another node that’s at address q: p->next = q; struct Node { string value; }; Node * next; Node *head, *second, *third; head = new Node; second = new Node; third = new Node; head->value = "books"; head->next = second; second->value = "shells"; second->next = third; third->value = "cash"; third->next = nullptr; int main(void) { } delete head; delete second; delete third; value next 8000 p value next 4000 q cout value; “blah” blah To link node p… to node q. 4000 To get the address of the node after p: Node *r = p->next; r 4000 To make node q a “terminal” node: q->next = nullptr; nullptr To free your nodes: delete p; delete q; Note: The delete command doesn’t kill the pointer… it kills what the pointer points to!

33 After all, some linked lists hold millions of items! That wouldn’t fit! Instead, we create a dedicated class (an ADT) to hold our linked list… And then add a bunch of member functions to add new items (one at a time), process the items, delete items, etc. OK, so let’s see our new class. 32 Linked Lists Normally, we don’t create our linked list all at once in a single function. struct Node { string value; }; Node * next; Node *head, *second, *third; head = new Node; second = new Node; third = new Node; head->value = "books"; head->next = second; second->value = "shells"; second->next = third; third->value = "cash"; third->next = nullptr; int main(void) { } delete head; delete second; delete third; We normally don’t create our linked list all at once like this.

34 33 A Linked List Class! int main(void) { } struct Node { string value; }; Node *next; delete head; delete second; delete third; Node *head, *second, *third; head = new Node; second = new Node; third = new Node; head->value = "books"; head->next = second; second->value = "shells"; second->next = third; third->value = "cash"; third->next = nullptr; First, in the simplest type of linked list class, the only member variable we need is a head pointer. class LinkedList { public: private: }; Node *head; Why? Given just the head pointer, we can follow the links to every node in the list. Ok, so let’s add a head pointer to our class. And since we can find all the nodes, we can also link in new ones, delete them, etc.. value next 5000 value next 2200 5000 value next 3700 "books" "shells" "cash" nullptr head 2200 3700 5000 This is all our class needs to hold! head

35 34 A Linked List Class! int main(void) { } Node *next; Chest *head, *second, *third; head = new Chest; second = new Chest; third = new Chest; head->treasure = "books"; head->nextChest = second; second->treasure = "shells"; second->nextChest = third; third->treasure = "cash"; third->nextChest = nullptr; class LinkedList { public: private: }; Alright, now what methods should our linked list class have? We need a constructor to create an empty list… LinkedList() { … } ~LinkedList() { … } And methods to add new items… void addToFront(string v) { … } void addToRear(string v) { … } And a method to delete items… void deleteItem(string v) { … } And a method to find if an item is in the list… bool findItem(string v) { … } And a method to print all the items… void printItems() { … } Node *head; And finally, we need a destructor to free all of our nodes! Let’s consider these one at a time!

36 35 Linked List Constructor OK, so what should our constructor do? Well, we’ll want it to create an “empty” linked list – one with no items. But how do we create an empty list? class LinkedList { public: private: }; Node *head; LinkedList() { … } ~LinkedList() { … } void addToFront(string v) { … } void addToRear(string v) { … } void deleteItem(string v) { … } bool findItem(string v) { … } void printItems() { … } {}…{}… Well, earlier I showed you how we marked the last node in a linked list… head 5000 value next 5000 "books" value next 2200 value next 3700 "shells" "cash" nullptr 2200 3700 Indicates that there aren’t any nodes following this one… We set its next value to nullptr.

37 value next 5000 "books" value next 2200 value next 3700 "shells" "cash" nullptr 2200 3700 36 Linked List Constructor So, following this logic… We can create an empty linked list by setting our head pointer to nullptr! class LinkedList { public: private: }; Node *head; LinkedList() { … } ~LinkedList() { … } void addToFront(string v) { … } void addToRear(string v) { … } void deleteItem(string v) { … } bool findItem(string v) { … } void printItems() { … } {}…{}… head = nullptr; head 5000 nullptr A head pointer with a value of nullptr means “empty list”! OK, next let’s learn how to print the items in our list!

38 head 37 So let’s assume we’ve used our class to create a linked list and add some items… 2000 value next 2000 value next 1200 value next 3700 "books" "shells" "cash" nullptr 1200 3700 Printing the Items in a Linked List class LinkedList { public: private: }; Node *head; ~LinkedList() { … } void addToFront(string v) { … } void addToRear(string v) { … } void deleteItem(string v) { … } bool findItem(string v) { … } void printItems() { … } LinkedList() { … } int main() { } LinkedList myList; // code to add nodes myList.printItems(); nullptr How do we go about printing the items in the list?

39 head 38 2000 Printing the Items in a Linked List class LinkedList { public: private: }; Node *head; ~LinkedList() { … } void addToFront(string v) { … } void addToRear(string v) { … } void deleteItem(string v) { … } bool findItem(string v) { … } void printItems() { … } LinkedList() { … } int main() { } LinkedList myList; // code to add nodes myList.printItems(); void printItems() {}{} value next 2000 value next 1200 value next 3700 "books" "shells" "cash" nullptr 1200 3700 So let’s assume we’ve used our class to create a linked list and add some items… How do we go about printing the items in the list?

40 Printing the Items in a Linked List p head 2000 class LinkedList { public: private: }; Node *head; {}{} void printItems() OK, so our goal is to loop through each of the nodes and print out their values, starting with the node pointed to by “head”… 39 But what kind of variable should we use? An integer? int i;// use this to loop? for (i=0;i<?????;i++) { … } Ah! Maybe we should use a Node pointer instead! Node *p; p = head; // p points to 1 st node while ( ) { } p points to a valid node print the value in the node p = address of the next node As with any loop, we’re going to need to use a variable to iterate. Now let’s just use a loop to cycle through the nodes! If we want to loop through the nodes from top to bottom, we need to start by pointing p at the top node! But how? Well, we know that our head pointer contains the address of the top node… So let’s just set p equal to it! value next 2000 value next 1200 value next 3700 "books" "shells" "cash" nullptr 1200 3700 First of all, we don’t even know how many items our list has… Hmmm. That doesn’t help! And second, our nodes are scattered randomly throughout memory! We need to find them one at a time to print them.

41 Printing the Items in a Linked List p head 2000 class LinkedList { public: private: }; Node *head; {}{} void printItems() OK, so our goal is to loop through each of the nodes and print out their values, starting with the node pointed to by “head”… 40 Node *p; p = head; // p points to 1 st node while ( { } p points to a valid node print the value in the node p = address of the next node value next 2000 value next 1200 value next 3700 "books" "shells" "cash" nullptr 1200 3700 2000 First, how can we determine if p points at a valid node? It’s easy, but let’s leave this for a bit later. ) Well, each node holds the location of the following node in its next variable. cout value << endl; OK, and how do we print out the value in the node? This one’s easy! So if p points to a node, we can find the address of the following node in p->next. p->next; And finally, how do we advance p so it points to the next node in the list? Alright, let’s trace through our loop! Be careful! You can’t use p++ to move forward in a linked list! You must use the next pointer!

42 Printing the Items in a Linked List p head 2000 class LinkedList { public: private: }; Node *head; {}{} void printItems() 41 Node *p; p = head; // p points to 1 st node while ( { } p points to a valid node print the value in the node p = address of the next node value next 2000 value next 1200 value next 3700 "books" "shells" "cash" nullptr 1200 3700 2000 cout value << endl; p->next; Well, p points to the “books” node at location 2000. It’s certainly valid. books The next node is at location 1200. 1200 p points to the “shells” node at location 1200. It’s certainly valid. shells The next node is at location 3700. 3700 cash The next node is at location nullptr. nullptr p != nullptr ) And there’s our complete printing loop! Any time we iterate through one or more nodes like this, it’s called a “traversal”. p points to the “cash” node at location 3700. It’s certainly valid. Whoa! p now points to nullptr. That’s not a valid node location! So this answers our question! If p’s value is nullptr, it does NOT point to a valid node. Otherwise it does. This is a linked list traversal! Alright, now let’s learn how to add nodes to our list! When we use the condition: while (p != nullptr) { … } the loop will process EVERY node in the list and only stop once it’s gone PAST the end of the list.

43 42 Adding an Item to a Linked List class LinkedList { public: private: }; Node *head; LinkedList() { … } ~LinkedList() { … } void addToRear(string v) { … } bool findItem(string v) { … } void deleteItem(string v) { … } void printItems() { … } head 2200 value next 2200 value next 3700 "shells" "cash" nullptr 3700 There are three places you can insert a new item into a linked list: at the top of the list at the end of the list somewhere in the middle The algorithm to insert at the top is the easiest to code and also runs the fastest. Let’s see this one first, and add a “ruby” to the top of our list! Here Or somewhere in the middle Allocate a new node void addToFront(string v) { … } Put value v in the node value next 8000 {}…{}… OK, so what’s our algorithm? Well, first we have to allocate a new node to hold our new value. Second, we need to put the new item into our new node. And finally let’s link our head pointer to our new node. Link the new node to the old top node Link the head pointer to our new top node “ruby" Then let’s link our new node to the old top node in the list. 2200 8000 These two steps must be in this order!

44 43 Adding an Item to the Front class LinkedList { public: private: }; Node *head; LinkedList() { … } ~LinkedList() { … } void addToRear(string v) { … } bool findItem(string v) { … } void deleteItem(string v) { … } void printItems() { … } head 2200 value next 2200 value next 3700 "shells" "cash" nullptr 3700 OK, now let’s replace our psuedo-code with valid C++ code. Allocate a new node Put value v in the node value next 8000 {}…{}… Link the new node to the old top node Link the head pointer to our new top node “ruby" void addToFront(string v) Node *p; p = new Node; p 8000 p->value = v; // put v in node To do that, we need to set p->next equal to the address of the top node in the existing list. p->next = Fortunately, the head variable holds the address of the current top node! head; 2200 Next, we want to link our new node to the current top node in the list. Finally, we just update our head pointer so it holds the address of our new top node! Fortunately, our p variable holds the address of our new node… head = p; 8000 And as you can see, our new node has been added at the top! We’ve already seen how to do this. Let’s define a temporary pointer and use the new command to allocate our new node. OK, now we just place item v (e.g., “ruby”) into our new node. That’s easy!

45 44 Adding an Item to the Front class LinkedList { public: private: }; Node *head; LinkedList() { … } head OK, but will this same algorithm work if the Linked List is empty? {}…{}… value next 2200 value next 3700 "shells" "cash" nullptr 3700 value next 8000 “ruby" void addToFront(string v) Node *p; p = new Node; p->value = v; // put v in node p->next = 8000 head = p; head; Let’s see! nullptr p value next 8000 “ruby" nullptr 8000 Pretty cool – the same algorithm works whether the list is empty or not! Our list has no nodes! It’s empty! 2200 Alright, now let’s see how to add a node to the rear of a list!

46 45 Adding an Item to the Rear class LinkedList { public: }; Node *head; LinkedList() { … } ~LinkedList() { … } void addToRear(string v) { … } bool findItem(string v) { … } void deleteItem(string v) { … } void printItems() { … } Alright, next let’s look at how to append an item at the end of a list… void addToRear(string v) { … } {}…{}… void addToFront(string v) { … } We’ll add our new node here… There are actually two cases to consider: Case #2: The existing list has one or more nodes… head 8800 value next 2200 "shells" value next 8000 “ruby" 2200 nullptr Case #2: The list already has some nodes! Case #1: The existing list is totally empty! head nullptr Case #1: The list has no nodes! We’ll add the very first node right here! private:

47 46 Adding an Item to the Rear class LinkedList { public: }; Alright, let’s consider Case #1 first… It’s much easier! { }... void addToRear(string v) head nullptr So how do you add a new node to the end of an empty linked list? In fact, it’s the same as adding a new node to the front of an empty linked list. After all, in both cases we’re adding a node right at the top of the linked list. if our linked list is empty then use our addToFront() code Which we just learned two minutes ago! OK, so how do we determine if our linked list is empty? if (head == nullptr) And this one is easy, we just call our addToFront() function! addToFront(v); // easy!!! head 2200 value next nullptr “ruby" 2200 Adding a node to the end of an empty list… Is the same as adding a node to the top of an empty list! Duh! Well, as we learned, in an empty linked list, the head has a value of nullptr.

48 47 Adding an Item to the Rear else { } class LinkedList { public: }; Alright, let’s consider Case #2 next: It’s more complex… value next 2200 value next 3700 "shells" "cash" 3700 value next “ruby" value next 8000 “ruby" 2200 head 8000 if (head == nullptr) addToFront(v); // easy!!! Here we want to add an item to the end of a linked list that already has nodes. Well that doesn’t look too bad... Let’s add an “iPad” to our list. OK, so what’s our algorithm? We have to traverse down the links until we find the current last node! p 8000 2200 3700 Use a temp variable to traverse to the current last node of the list Allocate a new node Put value v in the node value next 160 “iPad" Link the current last node to our new node nullptr 160 { }... void addToRear(string v) Well, in order to add a new node here… Then we want to allocate a new node and fill in its value… Next, we want to link the current last node to the new node. We want to add our new item right here! nullptr Link the last node to nullptr Notice that p doesn’t traverse past the last node... p points at it! Finally, we set our new node’s next pointer to nullptr.

49 48 Adding an Item to the Rear else { } class LinkedList { public: }; OK, let’s see the C++ code now! value next 2200 value next 3700 "shells" "cash" 3700 value next “ruby" value next 8000 “ruby" 2200 head 8000 if (head == nullptr) addToFront(v); // easy!!! p 8000 nullptr { }... void addToRear(string v) Use a temp variable to traverse to the current last node of the list Allocate a new node Put value v in the node Link the current last node to our new node Link the last node to nullptr Node *p; p = head; // start at top node So what should our traversal code look like? Next we want to traverse until p points directly at the last node… while(p isn’t at the last node) p = p->next; So what C++ code do we write here to ensure that our loop stops when p points right at the last node? But the next pointer in the earlier nodes is NOT nullptr! So we want to keep looping while p->next is not nullptr. The moment p->next is nullptr, p points at the last node! We want to stop looping when p points at this node Well, here’s a hint: Notice that the next pointer in the last node is nullptr. p->next != nullptr ) Well, as in our printItems() method, we’ll use a temp variable to follow the links, starting at the head node. void printItems() { Node *p; p = head; while (p != nullptr) { cout value << endl; p = p->next; } p points to a valid node)

50 49 Adding an Item to the Rear else { } class LinkedList { public: }; OK, let’s see the C++ code now! value next 2200 value next 3700 "shells" "cash" 3700 value next “ruby" value next 8000 “ruby" 2200 head 8000 if (head == nullptr) addToFront(v); // easy!!! p 2200 3700 nullptr { }... void addToRear(string v) Allocate a new node Put value v in the node Link the current last node to our new node Link the last node to nullptr while(p isn’t at the last node) Node *p; p = head; // start at top node p = p->next; p->next != nullptr ) OK, let’s see our C++ loop in action! OK, well so far, p->next is clearly not nullptr! Again, p->next is not yet nullptr! Oooh, p->next is finally equal to nullptr! And notice that p points directly at our last node! Alright, let’s finish up our function! 8000

51 50 Adding an Item to the Rear else { } class LinkedList { public: }; OK, let’s see the C++ code now! value next 2200 value next 3700 "shells" "cash" 3700 value next “ruby" value next 8000 “ruby" 2200 head 8000 if (head == nullptr) addToFront(v); // easy!!! p nullptr { }... void addToRear(string v) Allocate a new node Put value v in the node Link the current last node to our new node Link the last node to nullptr while(p isn’t at the last node) Node *p; p = head; // start at top node p = p->next; p->next != nullptr ) 3700 Alright, let’s finish up our function! How do we allocate a new node? That’s easy! Node *n = new Node; n 160 value next 160 Now let’s put our value into our new node… n->value = v; Now we need to link our last node to our new node… “iPad" We want to link this pointer… To our new node! p->next = n; 160 Finally, let’s terminate the linked list by setting the last node’s next pointer to nullptr. n->next = nullptr; nullptr When we use the condition: while (p->next != nullptr) { … } the loop continues until p points at the very last node of the list. When you get to the line after the loop, p points at the last node!

52 51 Not at the top, not at the bottom… In some cases, we won’t always want to just add our node to the top or bottom of the list… Why? Here’s the basic algorithm: void AddItem(string newItem) { } if (our list is totally empty) Just use our addToFront() method to add the new node Well, what if we have to maintain an alphabetized linked list, or we want to allow the user to pick the spot to put each item? In these cases, we can’t just add new items at the top or bottom… value next “bat” nullptr 600 head nullptr 600

53 Here’s the basic algorithm: 52 value next “dog” value next “cat” head 1000 1400 800 1400 value next “rat” nullptr 800 value next “bat” 600 void AddItem(string newItem) { } else if (our new node belongs at the very top of the list) Just use our addToFront() method to add the new node if (our list is totally empty) Just use our addToFront() method to add the new node 1000 For instance, let’s say we have an alphabetized list… and we want to insert an item that’s smaller than the rest of the items in the list… 600 1000 In this case, our addToFront() algorithm will add the node to the right spot in the list as well! Not at the top, not at the bottom… bat belongs here, above cat.

54 Here’s the basic algorithm: 53 p value next “dog” value next “cat” head 1000 1400 value next “rat” nullptr 800 1000 void AddItem(string newItem) { } else if (our new node belongs at the very top of the list) Just use our addToFront() method to add the new node if (our list is totally empty) Just use our addToFront() method to add the new node else // new node belongs somewhere in the middle of the list { } Use a traversal loop to find the node just ABOVE where you want to insert our new item For instance, we’re inserting an item like “fly” that belongs in the middle of an alphabetized list… value next “fly” 600 1400 1000 800 Once we’ve found the node directly above where we want to add our new node, we can… 600800 Link the new node into the list right after the ABOVE node Allocate and fill our new node with the item Not at the top, not at the bottom… First we link our new node to the node after it. Second we link our above node to our new node. fly belongs here, between dog and rat.

55 54 Let’s Convert it to C++ Code void AddItem(string newItem) { } else if (our new node belongs at the very top of the list) Just use our addToFront() method to add the new node else // new node belongs somewhere in the middle of the list { } Use a traversal loop to find the node just ABOVE where you want to insert your new item if (our list is totally empty) Just use our addToFront() method to add the new node if (head == nullptr) AddToFront(newItem); The code you write here depends on the nature of your list… Is it alphabetized? Is it ordered by student ID#? else if ( /* decide if the new item belongs at the top */ ) AddToFront(newItem); Similarly, your traversal code will depend on how your list is organized… Node *p = head; // start with top node while (p->next != nullptr) { p = p->next; // move down one node } if (/* p points just above where I want to insert my item */) break; // break out of the loop! Link the new node into the list right after the ABOVE node Allocate and fill your new node with the item Node *latest = new Node; // alloc and fill our new node latest->value = newItem; latest->next = This one is easy… We already did the same thing in our AddToRear() method. You’ll want an if-statement to break out of the loop when it finds the node just above the insertion point! value next “dog” value next “cat” head 1000 1400 value next “rat” nullptr 800 1000 value next 600 800 p 1400 800 This one’s easy – we’ve already learned how to allocate and fill in a new node variable. This one is a bit more complex… First we want to link our new node to the node below latest 600 “fly” p->next; // link new node to the node below We want to set latest->next… to the address of the node below… Second, we want to link the node above our insertion point to our new node We want to set p->next … to the address of our new node. p->next =latest; // link node above to our new node 600 These two lines must be in this order!

56 55 Let’s Convert it to C++ Code house *p = head; // start with first node while (p->next != nullptr) { p = p->next; // move down one node } else // new node belongs somewhere in the middle of the list { } void AddItem(string newItem) { } if (head == nullptr) AddToFront(newItem); else if ( /* decide if the new item belongs at the top */ ) AddToFront(newItem); Node *latest = new Node; // alloc and fill our new node latest->value = newItem; latest->next = p->next; // link new node to node below p->next = latest; // link above node to our new node Finally, let’s fill in the blanks to convert our function into one that adds items in alphabetical order! A new item belongs at the top of the list if it is smaller than the list’s current top item. Let’s check for this! newItem value ) if (/* p points just above where I want to insert my item */) break; // break out of the loop! if ( newItem >= p->value && newItem next->value ) break; This line checks if the new item should be inserted between the current node and the node below it… value next “rat” value next “cat” 1000 1400 p-> “dog”  The if-clause would be true for “dog” since it fits between “cat” and “rat”! “yak”  But false for “yak”, since it comes after “rat” and “cat”. … … value next “cat” head 1000 1400 1000 “bat”  This line ensures we stop when we’ve reached the last node in the list. If p->next == nullptr, it means that our new item is bigger than every item in the list! In this case we add our item after the last node. “yak”  value next “rat” nullptr value next “cat” 1000 1400 p-> Our updated traversal code should find just the right spot for our new item.

57 56 Deleting an Item in a Linked List class LinkedList { public: }; Node *head; LinkedList() { … } ~LinkedList() { … } void addToRear(string v) { … } bool findItem(string v) { … } void deleteItem(string v) { … } void printItems() { … } void deleteItem(string v) { … } {}…{}… When deleting an item from a linked list, there are two different cases to consider: Case #1: You’re deleting the first node. void addToFront(string v) { … } value next “dog” value next “cat” head 1000 1400 800 1400 value next “rat” nullptr 800 1000 Case #2: You’re deleting an interior node or the last node. We want to delete the item in the first node… We want to delete the item in an interior node… or the last node. Let’s consider Case #1 first… private:

58 57 Deleting an Item in a Linked List class LinkedList { public: }; {}…{}… value next “dog” value next “cat” head 1000 1400 800 1400 value next “rat” nullptr 800 void deleteItem(string v) X Ok, let’s consider Case #1… deleting the top item in a list. Let’s kill our cat. OK, so what’s our algorithm? If the list’s empty then return If the first node holds the item we wish to delete then { } If so, we create a temporary pointer to remember where our target node is located. killMe killMe = address of top node 1000 Update head to point to the second node in the list 1400 1000 head nullptr Well, first we need to make sure the list isn’t empty!!! If it is empty, there’s nothing to delete – so we just return! Then we update our head pointer… so it points to the node after our target node. Now we delete our target node… using our killMe pointer! Delete our target node Finally, let’s return once we’re done! Return – we’re done Next we need to check if the first node holds the item we want to remove.

59 58 Deleting an Item in a Linked List class LinkedList { public: }; {}…{}… void deleteItem(string v) If the list’s empty then return { } killMe = address of top node Update head to point to the second node in the list Delete our target node OK, let’s see the C++ code now! value next “dog” value next “cat” head 1000 1400 800 value next “rat” nullptr 800 1000 OK, so how do we determine if our linked list is empty? if (head == nullptr) return; Next, now how do we determine if the first node holds the value we want to delete? If the first node holds the item we wish to delete then if (head->value == v) Compares this value… against the input parameter. Now let’s create a temporary pointer and set its value to the top node’s address. Node *killMe = head; “cat” killMe 1000 Now let’s update our head pointer so it points to the second node. Let’s copy it into our head pointer. head = killMe->next; can be found in killMe->next! 1400 delete killMe; Now we can easily delete our target node! Return – we’re done Finally, let’s add our return statement. return; The second node’s address…

60 59 Deleting an Item in a Linked List class LinkedList { public: }; {}…{}… void deleteItem(string v) Alright, let’s consider Case #2 next – unfortunately it’s more complex… … // the code we just wrote value next “dog” value next “cat” 1000 1400 800 head 1000 Let’s kill our “rat” node! name next “yak” nullptr 3000 value next “rat” 3000 800 OK, so what’s our algorithm? Why the node above? Well, we need to stop one node above so we can relink around our target node! 3000 Use a temp pointer to traverse down to the node above the one we want to delete… p 1000 1400 killMe killMe = addr of target node 800 p now points just above our target node! Next we need to check if we even found our target node… If so, we can proceed! If we found our target node { } Ok, now let’s create another temp pointer to remember where our target node is. Link the node above to the node below Finally, let’s delete our target node! Delete our target node Ok, now let’s relink our node above the target… to the node below the target! Let’s kill this node! “rat” First we need to traverse down the list… until we find the node above the one we want to delete. ratnoise do not delete – sound effect

61 60 Deleting an Item in a Linked List class LinkedList { public: }; {}…{}… void deleteItem(string v) … // the code we just wrote value next “dog” value next “cat” 1000 1400 800 head 1000 name next “yak” nullptr 3000 value next “rat” 3000 800 p 1000 1400 OK, let’s see the C++ code now! Use a temp pointer to traverse down to the node above the one we want to delete… killMe = addr of target node If we found our target node { } Link the node above to the node below Delete our target node “rat” Node *p = head; while (p != nullptr) { p = p->next; } As before, let’s use one of our traversal loops. if ( ) break; // p pts to node above p->next != nullptr && p->next->value == v p will start by pointing at our head node… and will iterate through every node in the list… For each node we visit through p… If so, then p points to the node above our target… and we break out of our loop. First we check whether there is a node following the one pointed to by p. If there is a node following p… then we check to see if that next node holds our to-be- deleted value. If p->next is not nullptr… then there is a valid node following node p! p->next is not nullptr, so there is a valid node following this one! p->next->value is “dog” – this is not equal to our target value of “rat”. p->next is not nullptr, so there is a valid node following this one! p->next->value is “rat” – this is equal to our target value of “rat”! So we break out!

62 61 Deleting an Item in a Linked List class LinkedList { public: }; {}…{}… void deleteItem(string v) … // the code we just wrote value next “dog” value next “cat” 1000 1400 800 head 1000 name next “yak” nullptr 3000 value next “rat” 3000 800 p 1400 killMe 800 OK, let’s see the C++ code now! killMe = addr of target node {} {} Link the node above to the node below Delete our target node “rat” Node *p = head; while (p != nullptr) { p = p->next; } if ( ) break; // p pts to node above p->next != nullptr && p->next->value == v If we found our target node if (p != nullptr) // found our value! Note: The target node’s location can be found here! Node *killMe = p->next; But if we did find our target value, then p will point to the node above it, and NOT be nullptr! Let’s check for this case! p is not nullptr since we found our target! Let’s remember where our target node is located using a temporary pointer… Now we want to link the node above the target… to the node below the target. p->next =killMe->next; That address can be found in killMe->next! 3000 Finally, we can use the delete command to free our node… delete killMe; And believe it or not, this same code works when the target node is the last one in the list! If we traversed all the way through the list and didn’t find our value… then p will be equal to nullptr.

63 62 Linked List Challenge Now it’s your turn! Node *head; LinkedList() { … } ~LinkedList() { … } void addToRear(string v) { … } bool findItem(string v) { … } void deleteItem(string v) { … } void printItems() { … } bool findItem(string v) { … } {}…{}… void addToFront(string v) { … } private: How would you write the findItem() method? It should return true if it can find the passed-in item, and false otherwise. int main() { Linked List myFriends; myFriends.addToFront(“David”); … if (myFriends.findItem(“Carey”) == true) cout << “I’m so lucky!\n”; } class LinkedList { public: };

64 63 Destructing a Linked List class LinkedList { public: }; Node *head; LinkedList() { … } ~LinkedList() { … } void addToRear(string v) { … } bool findItem(string v) { … } void deleteItem(string v) { … } void printItems() { … } ~LinkedList() {}…{}… OK, so how do we completely destruct a linked list once we’re done with it? void addToFront(string v) { … } value next “dog” value next “cat” head 1000 1400 800 1400 value next “rat” nullptr 800 1000 private: Well, perhaps we can use something like our existing printItems() code? void printItems() { Node *p; p = head; while (p != nullptr) { cout value << endl; p = p->next; } Node *p; p = head; while (p != nullptr) { cout value << endl; p = p->next; } delete p; Let’s see what happens!

65 64 Destructing a Linked List class LinkedList { public: }; Node *head; ~LinkedList() {}…{}… OK, so how do we completely destruct a linked list once we’re done with it? value next “dog” value next “cat” head 1000 1400 800 1400 value next “rat” nullptr 800 1000 private: Well, perhaps we can use something like our existing printItems() code? Let’s see what happens! Node *p; p = head; while (p != nullptr) { delete p; p = p->next; } p 1000 Houston… We have a problem! There is no p->next anymore! That memory is no longer ours to use! In fact, there could be totally different data here now! 123456

66 65 Destructing a Linked List class LinkedList { public: }; Node *head; ~LinkedList() { OK, let’s fix it. value next “dog” value next “cat” head 1000 1400 800 1400 value next “rat” nullptr 800 1000 private: Node *p; p = head; while (p != nullptr) { delete p; p = p->next; p 1000 } … So before we delete the node pointed to by p… Let’s save the location of the next node in a temporary variable! Node *n = p->next; n 1400 delete p; Then we can delete our node normally… Finally, we can now advance our p pointer to the next node by using our temp variable! p = n; 1400 800

67 66 Linked Lists Aren’t Perfect! As you can already tell, linked lists aren’t perfect either! Well, as it turns out, we can fix this last problem… Let’s see how! First of all, they’re much more complex than arrays!!! Second, to access the k th item, I have to traverse down k-1 times from the head first! No instant access!!! And to add an item at the end of the list… I have to traverse through all N existing nodes first!

68 67 Linked Lists and Tail Pointers Since we have a head pointer… A tail pointer is a pointer that always points to the last node of the list! class LinkedList { public: LinkedList() {…} void addToFront(string v) {…} … private: Node *head; }; Why not maintain a “tail” pointer too? value next “cat” 1000 head 1000 value next “lemur” nullptr 800 1400 value next “emu” 1400 800 tail 800 Node *tail; Using the tail pointer, we can add new items to the end of our list without traversing!

69 68 Adding an Item to the Rear… With a Tail Pointer else { class LinkedList { public: private: Node *head; }; value next 2200 value next 3700 "shells" "cash" 3700 value next “ruby" value next 8000 “ruby" 2200 head 8000 if (head == nullptr) addToFront(v); tail nullptr { void addToRear(string v) while(p->next != nullptr) Node *p; p = head; // start at top node p = p->next; 3700 Let’s see how to update our addToRear() function once our class has a tail pointer. Node *n = new Node; value next 160 n->value = v; “iPad" p->next = n; n->next = nullptr; nullptr WARNING: You have to update all of your other methods to use the tail pointer (e.g., constructor, addToFront()) as well! Guess what – we no longer need this traversal loop! The tail pointer already points to our last node! Node *tail; Already points to our last node! n 160 Wait! We no longer have a p variable that points at the last node! But we do have our tail variable, and it points to the last node! So let’s just replace “p” with “tail”! tail->next = n; 160 So is that it??? Are we done??? Not quite! Now our tail pointer doesn’t point at our last node! Let’s fix it! Let’s update the tail pointer with the address of the new last node in the list. Guess what? n points to the last node now! 160 e.g., don’t forget to update addToFront(), deleteItem(), etc… tail = n; }... Node *n = new Node; n->value = v; n->next = nullptr; tail->next = n; tail = n; }...

70 69 Doubly-linked Lists A doubly-linked list has both next and previous pointers in every node: struct Node { string value; Node * next; Node * prev; }; One of the downsides with our simple linked list is that we can only travel in one direction… down! value next 2200 value next nullptr 3700 "shells" "cash" 3700 value next “ruby" value next 8000 “ruby" 2200 head 8000 Given a pointer to a node, I can only find nodes below it! p 2200 From p, I can find my shells and my cash, but have no way of getting back to my ruby! Wouldn’t it be nice if we could move both directions in a linked list? We can! With a doubly-linked list! value next 2200 value next nullptr 3700 "shells" "cash" 3700 value next “ruby" value next 8000 “ruby" 2200 head 8000 prev Each prev pointer points to the node above… Except the top node’s prev pointer… It has a value of nullptr, indicating that there are no nodes above it. 2200 8000 nullptr

71 70 Doubly-linked Lists value next 2200 value next nullptr 3700 "shells" "cash" 3700 value next “ruby" value next 8000 “ruby" 2200 head 8000 p value next 2200 value next nullptr 3700 "shells" "cash" 3700 value next “ruby" value next 8000 “ruby" 2200 head 8000 prev nullptr prev 8000 prev 2200 Now I can traverse in both directions! And, if I like, I can have a tail pointer too! tail 3700 Node *p; p = tail; while (p != nullptr) { cout value; p = p->prev; } Node *p; p = head; while (p != nullptr) { cout value; p = p->next; } Of course, now we’re going to have to link up lots of additional pointers… But nothing comes free in life!

72 71 Doubly-linked Lists: What Changes? Every time we insert a new node or delete an existing node, we must update three sets of pointers: 1. The new node’s next and previous pointers. 2. The previous node’s next pointer. 3. The following node’s previous pointer. And of course, we still have special cases if we insert or delete nodes at the top or the bottom of the list. Head Ptr Data Next Prev Bill Data Next Prev Jane Data Next Prev Myk nullptr Data Next Prev Dave nullptr Insert here! Previous Node Following Node

73 72 Linked List Cheat Sheet struct Node { string value; Node *next; Node *prev; }; Given a pointer to a node: Node *ptr; To see if ptr points to the last node in a list: if (ptr != nullptr && ptr->next == nullptr) then-ptr-points-to-last-node; To get to the next node’s data: if (ptr != nullptr && ptr->next != nullptr) cout next->value; Does our traversal meet this requirement? NODE *ptr = head; while (ptr != nullptr) { cout value; ptr = ptr->next; } To get the head node’s data: if (head != nullptr) cout value; To advance ptr to the next node/end of the list: if (ptr != nullptr) ptr = ptr->next; NEVER access a node’s data until validating its pointer: if (ptr != nullptr) cout value; To check if a list is empty: if (head == nullptr) cout << “List is empty”; Notice that this check: ptr != nullptr To check if a pointer points to the first node in a list: if (ptr == head) cout << “ptr is first node”; Ensures that ptr points to a valid node… before we access its “value” and “next” fields here.

74 73 Which is Faster? Getting to the 753 rd item in a linked list or an array? Linked Lists vs. Arrays Winner: Array We can get to any item in an array in 1 step. We have to pass thru 752 other nodes to reach the 753 rd node in a list! Which is Faster? Inserting a new item at the front of a linked list or at the front of an array? Winner: Linked List We can insert a new item in a few steps! With an array, we’d have to shift all n items down first! Which is faster? Removing an item from the middle of a linked list or the middle of an array? Winner: Linked List Once we’ve found the item we want to delete, we can remove it in a few steps! With an array, we’d have to shift all the following items up one slot! Which is easier to program? Which data structure will take less time to program and debug? Winner: Array Let’s face it – arrays are easier to use. So only use a linked list if you really have to!

75 74 Class Challenge Write a function called insert that accepts two NODE pointers as arguments: b4node: points to a node in a doubly-linked list newnode: points to a new node you want to insert When your function is called, it should insert newnode after b4node in the list, properly linking all nodes. (You may assume that a valid node follows b4node prior to insertion.) struct NODE { string data; NODE *next, *prev; }; b4node newnode Data Next Prev Dave exists nullptr


Download ppt "1 Lecture #4 Resource Management, Part 2 –Assignment Operators Basic Linked Lists –Insertion, deletion, destruction, traversals, etc. Advanced Linked."

Similar presentations


Ads by Google