1 / 68

Lecture #4

Lecture #4. Resource Management, Part 2 Assignment Operators Basic Linked Lists Insertion, deletion, destruction, traversals, etc. Advanced Linked Lists Tail Pointers Doubly-linked Lists. Time for your favorite game!. Programming Language Inventor Or Serial Killer.

deana
Download Presentation

Lecture #4

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  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

  2. Time for your favorite game! Programming Language Inventor Or Serial Killer See if you can guess who uses a keyboard and who uses a chainsaw!

  3. The Assignment Operator int main() { Circ foo(1,2,3); Circ bar(4,5,6); bar = foo; } int main() { Circ x(1,2,3); Circy = x; } Last time we learned how to construct a new class variable using the value of an existing variable. 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.

  4. The Assignment Operator int main() { Circ foo(1,2,3); Circ bar(4,5,6); bar = foo; } bar foo m_x m_x 4 5 6 1 2 3 m_y m_y 2 3 1 m_rad m_rad 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. Why isn’t bar’scopy constructor called? Because bar was already constructed on the line above! The variable already exists and is already initialized! If you don’t define your own assignment operator… Then C++ provides a default version that just copies each of the members. Lets see how to define our own assignment operator.

  5. The Assignment Operator 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 } int main() { Circ foo(1,2,3); Circ bar(4,5,6); bar.setMeEqualTo(foo); } 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 } Hmmm.. This looks familiar, doesn’t it? What does it remind you of? 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; }; The syntax for an assignment operator is a bit confusing. So lets define a simpler version first… void setMeEqualTo(const Circ &src) 2 1 3 1 2 3 { m_x = src.m_x; m_y = src.m_y; m_rad = src.m_rad; } Here’s how we’d use our new function. bar When we’re done, bar is a perfect clone of foo! 5 6 4 // same as bar = foo;

  6. The Assignment Operator operator= return(*this); Circ & 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!!! Now lets see what a real assignment operatorlooks like. 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 function name is operator= void setMeEqualTo (const Circ &src) { m_x = src.m_x; m_y = src.m_y; m_rad = src.m_rad; } 2. The function return type is a reference to the class. 3. The function returns *this when its done.

  7. The Assignment Operator bar foo class Circ { ... Circ &operator=(constCirc &src) { m_x = src.m_x; m_y = src.m_y; m_rad = src.m_rad; return(*this); } ... private: m_xm_ym_rad } 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 } 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; }; int main() { Circ foo(1,2,3); Circ bar(4,5,6); bar = foo; } void Assign (const Circ &src) return(*this); operator= { m_x = src.m_x; m_y = src.m_y; m_rad = src.m_rad; } Circ & Another way to read: bar = foo; is: bar.operator=(foo); i.e., we’re calling bar’soperator= member function! C++: “Ooh, the programmer is using the equal sign to set bar equal to foo.” 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! C++: “Since the programmer defined an assignment operator for Circles, I’ll use it to do the assignment.” bar.operator=(foo); 2 3 1 1 2 3 6 5 4

  8. The Assignment Operator 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; }; 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! Remember our updated Squares class… Lets see what happens if we use the default assignment operator with it…

  9. The Assignment Operator 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; }; int main() { Squares a(3); Squares b(4); b = a; } 3 m_n m_n m_sq m_sq 00000800 00000804 00000808 00000900 00000904 00000908 00000912 b a 800 Operating System, can you free the memory at address 800 for me. Operating System, can you free the memory at address 800 for me. Then b’s destructor is called. First a’s destructor is called. Oh – and by the way, you never freed the memory at 900, you know! // a’s d’tor is called, then b’s 1 You already asked me to do that! I can’t free it twice!!! ERROR!!!! 3 4 No sweat, homie. Consider it freed. 800 9 1 4 9 16 4 900

  10. The Assignment Operator 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; }; int main() { Squares a(3); Squares b(4); b = a; } 3 m_n m_n m_sq m_sq 00000900 00000904 00000908 00000912 00000800 00000804 00000808 b a 800 • When we copy a’s members into b, we have 2 problems: • Our b variable forgets where it reserved its memory. • Both a and bpoint to the same memory. 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! When a is destructed, it frees the memory at 800. But this memory is still being referred to by b! Utoh! // a’s d’tor is called, then b’s 1 3 4 800 9 ? 1 4 9 16 4 900

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

  12. The Assignment Operator General assignment operator syntax: Don’t forget the & symbol to make src a reference! 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!

  13. The Assignment Operator 3 m_n m_n m_sq m_sq 000900 000904 000908 000912 000800 000804 000808 000860 000864 000868 b a 860 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; } Operating System, I no longer need the memory at location 900. OS: Can you reserve 12 bytes of memory for me? Sure. Here’s 12 bytes of memory for you at address 860. 1 src Ok, I’ll free that up for someone else to use. 3 4 800 9 1 4 9 16 1 4 4 900 9

  14. The Assignment Operator 3 m_n m_n m_sq m_sq 000800 000804 000808 000860 000864 000868 b a 860 Operating System, I no longer need the memory at location 800. Operating System, you can free the memory at address 860. 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’s d’tor is called, then b’s … and everything is freed perfectly! 1 3 4 Ok, I’ll free that for you. Ok, I’ll free that up for someone else to use. 800 9 1 4 4 900 9

  15. 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; }; tim sam ted class CSNerd { CSNerd &operator= (const CSNerd &src) { m_numPCs = src.m_numPCs; m_hasMac = src.m_hasMac; return(*this); } m_numPCs m_hasMac }; class CSNerd { CSNerd &operator= (const CSNerd &src) { m_numPCs = src.m_numPCs; m_hasMac = src.m_hasMac; return(*this); } m_numPCs m_hasMac }; class CSNerd { CSNerd &operator= (const CSNerd &src) { m_numPCs = src.m_numPCs; m_hasMac = src.m_hasMac; return(*this); } m_numPCs m_hasMac }; And this line returns the tim variable, so if we wanted, we could do yet another assignment! tim So this line returns the ted variable itself! Strange huh? A member function of a variable can return the variable itself!?!? “this” is a special C++ pointer variable that holds the address of the current object (i.e., ted’s address in RAM) Question: Why do we have return(*this) at the end of the assignment operator function? So if “this” is a pointer to ted, then “*this” refers to the whole ted variable. 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. So the statement: “ted = sam” isjust replaced by the ted variable! Answer: So we can do multipleassignments in the samestatement, like this… All assignment is performed right-to-left… true 3 5 false First we call ted’s assignment operator to assign him to sam. ted Next, C++ sets tim equal to ted. int main() { CSNerdsam(5,false); CSNerd ted(10,false); CSNerdtim(3,true); tim = ted = sam; } false false 5 10 bill = false 5

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

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

  19. 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; } ~CompSciStudent() { delete m_myBook; } private: Book *m_myBook; string m_myName; };

  20. Linked Lists

  21. Pointers and Trains! As we’ll see during our next segment, self-referential structures, like our RailRoadCar, are essentialto create linked list data structures. structTrainCar { string myCargo; }; Consider a railroad/train car. Every railroad car holds some cargo and has a link to the next railroad car in the train. This definition says that each railroad car contains a pointer to the next railroad car. TrainCar* nextCar; This is called a self-referential data-type, since the struct refers to itself within its field definitions. I know it looks funny, but it’s totally legal. Let’s see how it can be used! Can we define a C++ struct to represent a railroad car? Let’s see!

  22. Pointers and Trains! curCar caboose boxcar engine myCargo myCargo myCargo nextCar nextCar nextCar As we’ll see during our next segment, self-referential structures, like our TrainCar, are essentialto create linked list data structures. structTrainCar { string myCargo; }; int main(void) { TrainCarengine, boxcar, caboose; engine.myCargo= “coal”; engine.nextCar = &boxcar; boxcar.myCargo = “gold”; boxcar.nextCar = &caboose; caboose.myCargo = “pigs”; TrainCar*curCar; curCar = &engine; cout << curCar->myCargo; curCar = curCar->nextCar; cout << curCar->myCargo; curCar = curCar->nextCar; cout<< curCar->myCargo; } 1000 1000 TrainCar* nextCar; “coal” 1020 1020 1020 “gold” 1040 1040 1040 “pigs” myCargo curCar -> nextCar curCar -> myCargo curCar -> -> curCar nextCar myCargo curCar -> pigs coal gold

  23. Linked List Analogy Einstein is expecting a shipment of thousands of animals. He’d like to rent a large block of houses to hold all the animals, but he doesn’t know how many animals there will be, so he can’t rent them all at once. Instead, he’s decided to rent a new house as each animal arrives. In this way, he only needs to keep the address of his latest rented house in order to locate all of his animals. Since he may have to rent thousands of houses, he doesn’t want to keep a list of every house address he’s rented. So every time he rents a new house, he’ll put the new animal there and then he’ll tack the address of the last house he rented on the refrigerator. Let’s see this example in action!

  24. 1000 Gayley 1100 Gayley 1300 Gayley 1200 Gayley 1400 Gayley End Of List Einstein That’s the last address in my list, so I’ll put a note on the fridge there to remind me. Ok. I’ll remember that address and put the cow there. I need a house to hold a cow. Last Rented House: 1200 Gayley is vacant. Use it. 1200 Gayley

  25. 1000 Gayley 1100 Gayley 1300 Gayley 1200 Gayley 1400 Gayley End Of List Next: Ok, I’ll put the emu at 1000. I’ll also put a note on the fridge there that reminds me where my Cow is. So now we have a single animal in our list. The chalk board tells us the address of this animal. Finally, I’ll update my lastRented so I remember where my newest animal is staying. Now I need a house to hold an emu. Last Rented House: 1000 Gayley is vacant. Use it. 1200 Gayley 1200 1000 Gayley

  26. 1000 Gayley 1100 Gayley 1300 Gayley 1200 Gayley 1400 Gayley Next: End Of List Next: 1200 Ok, I’ll put the mouse at 1400. I’ll also put a note there that reminds me where my emu is. Now we have two animals in our list. The second we added is now at the top of the list. Finally, I’ll update my lastRented so I remember where my newest animal is staying. Now I need a house to hold a mouse. Last Rented House: 1400 Gayley is vacant. Use it. 1000 1000 Gayley 1400 Gayley

  27. 1000 Gayley 1100 Gayley 1300 Gayley 1200 Gayley 1400 Gayley Next: Next: End Of List 1000 1200 What we have here is a “linked list” of animals… Starting with the address on the chalk board, we can follow the clues on each note like a scavenger hunt. Last Rented House: 1000 Gayley 1000 1400 Gayley First we find the mouse, then we find the emu, and finally the cow.

  28. 1000 Gayley 1100 Gayley 1300 Gayley 1200 Gayley 1400 Gayley End Of List Next: Next: 1000 1200 In this example, we added each new animal to the top of our list. Last Rented House: We could have just as easily added animals to the middle or end of the list by using a slightly different algorithm. 1000 Gayley 1000 1400 Gayley

  29. Einstein’s “Algorithm” So what’s Einstein’s “algorithm” for adding an animal? • Rent a new empty house to store his new animal. 2. Put his animal in the empty house. 3. Tack a note to the fridge in the new house to point to the last house where he put an animal. 4. Update his chalk board with the address of his newest house. We can do the same thing with C++…

  30. Einstein’s “Algorithm” in C++ A self-reference! (e.g. choochoo train) Why not use a C++ struct? Any ideas? • First, we need a way to represent a house in C++. Each house holds an animal and the next house’s address: struct house { string name; // animal we’re holding house *next; // address of next house }; • We use the new command to ask for a new house struct to hold a new animal. • We use a special “last rented” pointer to keep track of the last house added to the list. • We use the nullptrpointer to identify the last struct in the list.

  31. To add an animal to a linked list… • Request a new empty house to store our new animal. struct 2. Put our animal in the empty house. newstruct. pointer newstruct 3. Set the note in the new house to point to the last house where we put an animal. struct 4. Update our chalk-board with the address of the newest house. last rented pointer struct. house *lastRented; // points to the last house we rented void add_animal(string animal) { } struct house { string name; house *next; }; house *latest = new house; // get new house latest->name = animal; // store the animal latest->next = lastRented; // tack note on fridge lastRented = latest; // remember where our // newest house is!

  32. Our first Linked List lastRented latest 1200 1200 name next NONE Last Rented House: There was no previous house, so this value is nullptr. struct house { string name; house *next; }; house *lastRented; // global void add_animal(string animal) { house *latest = new house; latest->name = animal; latest->next = lastRented; lastRented = latest; } int main(void) { lastRented = nullptr; add_animal(“cow”); add_animal(“emu”); add_animal(“mouse”); } nullptr nullptr Tack a note with the address of the last house we rented onto the fridge… Hey Operating System – I need about 20 bytes to hold a housestruct. Now that we rented a new house, let’s jot down it’s address. “cow” So far, we haven’t rented any houses! 1200 latest-> “cow” Sure. I’ll reserve 20 bytes of memory for you at address 1200.

  33. Our first Linked List lastRented latest 1000 1000 name name “cow” next next nullptr Our previous rented house was at 1200, so we’ll put that on our fridge. struct house { string name; house *next; }; house *lastRented; // global void add_animal(string animal) { house *latest = new house; latest->name = animal; latest->next = lastRented; lastRented = latest; } int main(void) { lastRented = nullptr; add_animal(“cow”); add_animal(“emu”); add_animal(“mouse”); } Now that we rented a new house at 1000, let’s remember it’s address. (we can forget the old house at 1200 now). 1200 1200 Tack a note to the fridge that has the address of the last house we rented… Hey Operating System – I need about 20 bytes to hold a housestruct. “emu” 1000 latest-> “emu” 1200 Sure. I’ll reserve 20 bytes of memory for you at address 1000.

  34. Our first Linked List lastRented latest 1400 1400 name name name “emu” “cow” next next next nullptr 1200 Our previous rented house was at 1000, so we’ll put that on our fridge. struct house { string name; house *next; }; house *lastRented; // global void add_animal(string animal) { house *latest = new house; latest->name = animal; latest->next = lastRented; lastRented = latest; } int main(void) { lastRented = nullptr; add_animal(“cow”); add_animal(“emu”); add_animal(“mouse”); } Now that we rented a new house at 1400, let’s remember it’s address. (we can forget the old house at 1000 now). 1000 1000 Tack a note to the fridge that has the address of the last house we rented… Hey Operating System – I need about 20 bytes to hold a housestruct. “mouse” 1000 1200 Sure. I’ll reserve 20 bytes of memory for you at address 1400. 1400 latest-> “mouse”

  35. Our first Linked List lastRented 1000 1200 name name name “emu” “cow” next next next nullptr 1200 1400 Our lastRented pointer (also called a “head pointer”) points to the firststruct in the list. head 1400 Each struct holds an animal’s name and a next pointer that points to the next house in the list. … The last struct in the list has a next pointer of nullptr. This means “end of list.” “mouse” 1000

  36. What can we do to a linked list? We can add items to the list To the front, middle, or end We can add items to the list To the front, middle, or end Typically you create a class to hold your linked list. We can delete an item from it By value or by slot number We can delete an item from itBy value or by slot number class EinsteinsLinkedList { public: private: }; We can search for an item in it We can search for an item in it We can process the items in it Print it, add all its items, etc. We can process the items in it Print it, add all its items, etc. void addItemToFront(string animal); void deleteItem(int itemNum); void deleteItem(string whichAnimal); We can destroy the list Remove all of its items at once We can destroy the list Remove all of its items at once int findItem(string animalToFind); And of course, we can create a new list! And of course, we can create a new list! void printItems(); ~EinsteinsLinkedList(); EinsteinsLinkedList();

  37. What can we do to a linked list? struct house { string name; house *next; }; Last Rented House: Every linked list needs to hold at least one piece of private data… Typically you create a class to hold your linked list. What is it? class EinsteinsLinkedList { public: private: }; The “last rented” or “chalkboard” pointer… This pointer points to the top item in the list. The “last rented” or “chalkboard” pointer… This pointer points to the top item in the list. void addItemToFront(string animal); void deleteItem(int itemNum); void deleteItem(string whichAnimal); Again, this is typically called a “head” pointer since it points to the head/start of the list! int findItem(string animalToFind); And of course, we can have other (optional) member variables too… void printItems(); ~EinsteinsLinkedList(); EinsteinsLinkedList(); house *m_head; house *lastRented; int m_numItems;

  38. Using our Linked List Class e e e e m_head m_head m_head m_head nullptr 2 1 0 3 m_numItems m_numItems m_numItems m_numItems name name name “cat” “cow” “emu” 2 next next next nullptr struct studentNode { int student ID; string firstName, lastName; float GPA; studentNode *next; }; class UCLARosterList { public: private: }; In CS Lingo, these are called nodes. struct house { string name; house *next; }; void addStudent(string first, string last, int ID, float GPA); void deleteStudent(string first, string last); void deleteStudent(int ID); bool findStudent(int ID, float &GPA); bool findStudent(int ID, string &name); void printRoster(); ~UCLAStudentRoster(); UCLAStudentRoster(); studentNode *m_head; int main() { UCLARosterList cs31, cs32; cs31.addStudent(“Lisa”, “Lu”, 123456, 3.99);cs32.addStudent(“David”,”Smallberg”, 989898, 1.50); ... } int main() { EinsteinsLinkedList e; e.addItemToFront(“cow”); e.addItemToFront(“emu”); e.addItemToFront(“cat”); e.deleteItem(“emu”); } Oh, and by the way, we can store more than just a single string in our nodes!

  39. Initializing a Linked List m_head nullptr same thing Last Rented House: Any time you create a new linked list, the first thing you have to do is initialize it! To do this, we set our head pointer to nullptr. This indicates that there are no items in the list. class EinsteinsLinkedList { public: ... private: house *m_head; }; EinsteinsLinkedList() // c’tor { } m_head = nullptr; None yet!

  40. Inserting a Node into a Linked List • There are three places you can insert a new node into a linked list: • always at the top of the list (Einstein’s approach) • always at the end of the list • or somewhere in the middle The algorithm to insert at the top is the easiest to code and also runs thefastest. The algorithms to insert in the middle/end of the list are slower and more complex to code. It’s always best to choose the simplest insertion algorithm possible that suits your goal.

  41. Inserting at the Top (Einstein’s Algorithm) name “cat” name next next Operating system, can you allocate 20 bytes of memory for me to hold a house variable? When inserting a new item at the top of a linked list, there are two possible cases. Case #1: You’re adding a node to the top of a list that already has one or more nodes. class EinsteinsLinkedList { public: ... void AddItemToFront(string animal) { house *latest = new house; latest->name = animal; latest->next = m_head; m_head = latest; // link the head } private: house *m_head; }; 100 100 m_head 700 latest 700 700 “dog” 100 200 200 “emu” name No problem. Here’s a reserved slot at location 700. 500 next 500 Now let’s see the second case! “cow” name nullptr next

  42. Inserting at the Top name next Case #2: You’re adding a node to the top of an EMPTY list. class EinsteinsLinkedList { public: ... void AddItemToFront(string animal) { house *latest = new house; latest->name = animal; latest->next = m_head; m_head = latest; // link the head } private: house *m_head; }; m_head nullptr nullptr latest 700 700 700 “dog” Cool huh? Einstein’s same four-line algorithm works: whether the list is empty or whether it already has items!

  43. Adding a New Node at the End of a List nullptr m_head Now let’s learn how to insert a new nodeat the end of a linked list. Again, there are two cases you must consider! Case #1:The list is empty before you add your new node. class EinsteinsLinkedList { public: void AddItemToEnd(string animal) { … } private: house *m_head; }; // list is empty, use Einstein’s alg if (m_head == nullptr) AddItemToFront(animal); In this case, we can just use Einstein’s insert-at-top algorithm (from 20 sec ago!) Why? Inserting at the end of an empty list is the same as inserting at the top of an empty list!

  44. Inserting a New Node at the End of a List name “cat” name next next m_head But what if the list already has nodes? Case #2: If the list is not empty, then: 1. Follow the links of the list down the until you reach the last node of the list. 2. Allocate a new node using new. “emu” name 3. Add data to the new node next 4. Set your new node’s next pointer to nullptr! 5. Update the last node in the list to point to the new node. “cow” name 700 nullptr next 700 “dog” nullptr Pretty easy, right? The only challengeis following the links to the last node…

  45. Finding the Last Node of a Linked List latest temp name “cat” name next next Notice that temp->next is not nullptryet because this node is not the last node in the list! To find the last node, we’ll use a technique called “list traversal.” m_head 1000 1000 Our traversal starts with the first node in the linked list. All list traversals use a loop to step from node to node. All traversals use a temporary variable to point to the various nodes in the list… Now that we’ve found the last node in our list, let’s add our new node after it! 1000 1400 1400 house *temp; temp = m_head; // point temp to 1st node 1400 “emu” name Note: We must make sure our new node has a next pointer of nullptr– this marks it as the last node! while (temp->next != nullptr) { } 800 temp next -> 800 800 next temp = temp->next; So we break out of our loop… Finally, we set our previous last node to point to our new last node! 800 “cow” name This part of the traversal advances temp to the next node of the list. Be careful! You can’t usetemp++ to move forward in a linked list! You must use: temp = temp->next Our traversal continues until the temp variable points at the last node in the list. We’ll know when temp points to the last node because it has a next value of nullptr. nullptr next house *latest = new house; latest->name = animal; latest->next = nullptr; temp->next = latest; Aha! We’ve finally reached the last node in the list. temp now points to it! 700 700 “dog” 700 nullptr

  46. Inserting at the End(Complete Version) class EinsteinsLinkedList { public: ... void AddItemToEnd(string animal) { // if our list is empty, use Einstein’s alg if (m_head == nullptr) AddItemToFront(animal); else { // traversal: find last node in the list house * temp = m_head; while (temp->next != nullptr) { temp = temp->next; } // now link the last node to our new node! house * latest = new house; // allocate latest->name = animal; // initializelatest->next = nullptr; // new end-node temp->next = latest; // link it in! } } Ok… So we saw how to insert at the top… And we saw how toinsert at the end… What about insertingin the middle of a list? Let’s see how.

  47. Not at the top, not at the bottom… 600 “bat” nullptr name next void AddItem(string &newItem) { } m_head In some cases, we won’t always want to just add our node to the top or bottom of the list… Why? Well, for example, if our list is alphabetized, when we insert a new item, we want it to be in the right order – we may have to put it somewhere in the middle! Here’s the basic algorithm: 600 nullptr if (our list is totally empty) Just use Einstein’s algorithm to add the new node

  48. Not at the top, not at the bottom… m_head 1000 1400 1400 1400 “dog” name name “cat” 800 name next next void AddItem(string &newItem) { } next 800 “rat” name nullptr next 600 “bat” 600 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… In this case, Einstein’s algorithm will properly modify the m_head pointer and insert the node before the other nodes in the list. 1000 nullptr Here’s the basic algorithm: if (our list is totally empty) Just use Einstein’s algorithm to add the new node else if (our new node belongs at the very top of the list) Just use Einstein’s algorithm to add it there and we’re done

  49. Not at the top, not at the bottom… 1000 m_head 1000 1400 1400 above name 1400 “dog” name “cat” name next next void AddItem(string &newItem) { } next 800 “rat” name nullptr next 600 “fly” 1000 Second we link our above node to our new node. First we link our new node to the node after it. For instance, we’re inserting an item like “fly” that belongs in the middle of an alphabetized list… 1400 Here’s the basic algorithm: 800 600 800 Once we’ve found the node directly above where we want to add our new node, we can… if (our list is totally empty) Just use Einstein’s algorithm to add the new node else if (our new node belongs at the very top of the list) Just use Einstein’s algorithm to add it there and we’re done 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 Allocate and fill your new node with the item Link the new node into the list right after the ABOVE node

  50. Let’s Convert it to C++ Code This one is easy… We already did the same thing in our AddItemToEnd function. The code you write here depends on the nature of your list… Is it alphabetized? Is it ordered by student ID#? void AddItem(string &newItem) { } if (m_head == nullptr) AddItemToFront(newItem); // use Einstein’s algorithm if (our list is totally empty) Just use Einstein’s algorithm to add the new node Similarly, your traversal code will depend on how your list is organized… else if (our new node belongs at the very top of the list) Just use Einstein’s algorithm to add it there and we’re done else if ( /* decide if the new item belongs at the top */ ) AddItemToFront(newItem); // use Einstein’s algorithm else // new node belongs somewhere in the middle of the list { } house *above = m_head; // start with first node while (above->next != nullptr) { above = above->next; // move down one node } Use a traversal loop to find the node just ABOVE where you want to insert your new item This one’s easy – we’ve already learned how to allocate and fill in a new node variable. // add your customized traversal code here; // it should break out of the loop once it finds // the node directly above our insertion point! • This one is a bit more complex, it: • Links our new node to the node below • Links the node above to our new node house *latest = new house; // alloc and fill our new node latest->name = newItem; Allocate and fill your new node with the item latest->next = above->next; // link new node to following node above->next = latest; // link above node to new node Link the new node into the list right after the ABOVE node

More Related