Post on 11-Jan-2016
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
The Assignment OperatorLast 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;}
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_xm_y
m_rad
123
bar m_xm_y
m_rad
456
123
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!
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 31
5
2 3
When we’re done, bar is a perfect clone
of foo!
foo
4
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)
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 31
65
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!
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…
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
3000008000000080400000808800
149
b m_n
m_sq
4900
00000900000009040000090800000912
149
16
3800
// 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.
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
3000008000000080400000808800
149
b m_n
m_sq
4900
00000900000009040000090800000912
149
16
3800
// 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!
?
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 memoryB. Opens system resources (like opening a file)
You need to define your own assignment operator
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!
12
The Assignment Operatorclass 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
3000800000804000808800
149
b m_n
m_sq
4900
000900000904000908000912
149
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. 000860000864000868
860
149
13
The Assignment Operatorclass 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
3000800000804000808800
149
b m_n
m_sq
4900
3 000860000864000868
860
149
// 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!
14
The Assignment Operatorclass 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 samestatement, 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?
samclass 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.
5 false
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 =
15
The Assignment OperatorOur 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
3000800000804000808800
149
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.
000420000424000428
So what values are at location 420-
428??RANDOM ones!
420-521934
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!
16
The Assignment OperatorThe 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
17
Copy Constructor/ Assignment Review
Question: which of the following use the copy constructor and which use the assignment
operator? int main() // #1{ Squares a(4), b(3); b = a;} int main() // #2{ Squares c(5), d(c); Squares e = d;}
// #3Squares func(void) { Squares g(15); return(g);} int main(){ Squares f = func();}
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;};
19
Linked Lists
20
Arrays are great… But…
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 w
aste
of sp
ace!
And what if you need to insert a new item in the middle of an
array?
AndrewBettyDavidElaineFrank
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 wantto 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!
21
So Arrays Aren’t Always Great
Hmm… Can we think of an approach from “real life” that
works better than a fixed-sized array?
What can we think of that:
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.
22
So Arrays Aren’t Always Great
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
23
So Arrays Aren’t Always Great
?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…
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.
25
struct Chest{ };
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.
26
treasure“books”
nextChest 3400
Clue:
The next item is by the tower.
struct Chest{ };
Well, we can use a C++ struct to represent a Chest.
Chest *first; // pointer to our 1st 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!
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";
}
chest1treasure
nextChest
chest2 treasure
nextChest
“books"1000
10201020
chest3treasure
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.
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
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
3700
OK, now let’s add cargo and link ‘em up!
first->treasure = "books";first->nextChest = second;
"books"
2200second->treasure = "shells";second->nextChest = third;
"shells"
3700third->treasure = "cash";
"cash"third->nextChest = nullptr;
nullptr
head
head
The pointer to the top item in the linked list is traditionally called the
“head pointer.”
head
headhead
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->treasurenextChest
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.
30 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”.
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; };
31
Linked ListsBefore 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 8000
value
next
4000
q 4000
cout << p->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!
32
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.
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.
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
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!
35
Linked List ConstructorOK, 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
5000value
next
5000"books"
value
next
2200
valuenext
3700
"shells"
"cash"nullptr
2200
3700
Indicates that there
aren’t any nodes
following this one…
We set its next value to nullptr.
36
value
next
5000"books"
value
next
2200
valuenext
3700
"shells"
"cash"nullptr
2200
3700
Linked List ConstructorSo, 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
5000nullptr
A head pointer with
a value of nullptr
means “empty
list”!
OK, next let’s learn how to print the items in our list!
37
head
So let’s assume we’ve used our class to create a linked list and add some
items…
2000valuenext
2000
valuenext
1200
valuenext
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?
38
head2000
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()
{
}valuenext
2000
valuenext
1200
valuenext
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?
39
Printing the Items in a Linked List
p
head 20002000
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”…
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 1st 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!
valuenext
2000
valuenext
1200
valuenext
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.
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”…
Node *p;p = head; // p points to 1st node
while ( {
}
p points to a valid node
print the value in the node
p = address of the next node
valuenext
2000
valuenext
1200
valuenext
3700
"books"
"shells"
"cash"nullptr
1200
37002000
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 << p->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!
41
Printing the Items in a Linked List
p
head 2000
class LinkedList{
public:
private:
};Node *head;
{
}
void printItems()
Node *p;p = head; // p points to 1st node
while ( {
}
p points to a valid node
print the value in the node
p = address of the next node
valuenext
2000
valuenext
1200
valuenext
3700
"books"
"shells"
"cash"nullptr
1200
37002000
cout << p->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.
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
valuenext
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
Here
Or somewhere
in the middle
Allocate a new nodevoid 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 theold top nodeLink the head pointer toour new top node“ruby" Then let’s link our new node
to the old top node in the list.22008000
These two steps must be in this order!
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
valuenext
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 theold top nodeLink the head pointer toour 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 setp->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!
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
valuenext
3700
"shells"
"cash"nullptr
3700
value
next
8000“ruby"
void addToFront(string v)
Node *p;p = new Node; p->value = v; // put v in nodep->next =
8000
head = p;
head;
Let’s see!
nullptr
p
value
next
8000“ruby"nullptr
8000
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!
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
nullptrCase #1:
The list has no nodes!
We’ll add the very first node
right here!
private:
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 thenuse 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
2200value
next nullptr
“ruby" 2200Adding 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.
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
valuenext
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 nodePut value v in the node
value
next
160“iPad"
Link the current last node to our new node
nullptr160
{
}...
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
nullptrNotice that p doesn’t traverse
past the last node... p points at it!
Finally, we set our new node’s next pointer to nullptr.
48
Adding an Item to the Rear
else{
}
class LinkedList{
public:
};
OK, let’s see the C++ code now!
value
next
2200
valuenext
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 nodePut value v in the nodeLink the current last node to our new nodeLink 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 << p->value << endl; p = p->next; }}
49
Adding an Item to the Rear
else{
}
class LinkedList{
public:
};
OK, let’s see the C++ code now!
value
next
2200
valuenext
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 nodePut value v in the nodeLink the current last node to our new nodeLink 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 isnot yet nullptr!
Oooh, p->next isfinally equal to
nullptr!
And notice that p points directly at our
last node!
Alright, let’s finish up our function!
8000
50
Adding an Item to the Rear
else{
}
class LinkedList{
public:
};
OK, let’s see the C++ code now!
value
next
2200
valuenext
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 nodePut value v in the nodeLink the current last node to our new nodeLink 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
160value
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!
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
headnullptr600
52
Here’s the basic algorithm:value
next
“dog”
value
next
“cat”
head
1000
1400
1400
800
1400
value
next
“rat”
nullptr
800
valuenext
“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
1000In 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.
53
Here’s the basic algorithm:
p
value
next
“dog”
value
next
“cat”
head
1000
1400
14001400
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…
valuenext
“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.
54
Let’s Convert it to C++ Codevoid 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 nodeif (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 nodewhile (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 itemNode *latest = new Node; // alloc and fill our new nodelatest->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
14001400
value
next
“rat”
nullptr
800
1000
valuenext
600
800
p1400
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!
55
Let’s Convert it to C++ Code
house *p = head; // start with first nodewhile (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 nodelatest->value = newItem;latest->next = p->next; // link new node to node belowp->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 < head->value )
if (/* p points just above where I want to insert my item */) break; // break out of the loop!if ( newItem >= p->value && newItem <= p->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
14001400
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
14001400
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
14001400
p->
Our updated traversal code should find just the right spot for our new item.
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
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:
57
Deleting an Item in a Linked List
class LinkedList{
public:
};
{
}…
value
next“dog”
value
next
“cat”
head
1000
1400
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 returnIf 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 node1000
Update head to point to thesecond node in the list
1400
1000
headnullptr
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.
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 thesecond 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
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…
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
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 traversedown to the node above theone we want to delete…
p
10001400
killMe
killMe = addr of target node800p 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 tothe 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.
ratnoisedo not delete – sound effect
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
1400
800
head 1000
name
next
“yak”
nullptr
3000
value
next
“rat”
3000
800
p
10001400
OK, let’s see the C++ code now!
Use a temp pointer to traversedown to the node above theone we want to delete…
killMe = addr of target node
If we found our target node{
}
Link the node above tothe node belowDelete 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!
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
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 tothe node belowDelete 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 nodeif (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.
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:
};
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
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 << p->value << endl; p = p->next; }}
Node *p; p = head; while (p != nullptr) { cout << p->value << endl; p = p->next; }
delete p;
Let’s see what happens!
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
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!
123456123456
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
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
800
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 kth 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!
67
Linked Lists and Tail PointersSince 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?
valuenext
“cat”
1000head
1000
valuenext
“lemur”
nullptr
800
1400
1400
valuenext
“emu”
1400
800
800
tail 800
Node *tail;Using the tail pointer, we can add new items to the end of our list without traversing!
68
Adding an Item to the Rear…With a Tail Pointer
else{
class LinkedList{
public:
private: Node *head;
};
value
next
2200
valuenext
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!n160
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; }}...
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
valuenext 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
valuenext nullptr
3700
"shells"
"cash"
3700
value
next
“ruby"value
next
8000“ruby"
2200
head
8000
prev
prev
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
70
Doubly-linked Lists
value
next
2200
valuenext nullptr
3700
"shells"
"cash"
3700
value
next
“ruby"value
next
8000“ruby"
2200
head
8000
p
8000value
next
2200
valuenext 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 << p->value; p = p->prev;}
Node *p;
p = head;while (p != nullptr){ cout << p->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!
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
DataNextPrev
Bill DataNextPrev
Jane DataNextPrev
Myknullptr
DataNextPrev
Dave
nullptr
Insert here!
PreviousNode
FollowingNode
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 << ptr->next->value;
Does our traversal meet this requirement?
NODE *ptr = head; while (ptr != nullptr) { cout << ptr->value; ptr = ptr->next; }
To get the head node’s data: if (head != nullptr) cout << head->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 << ptr->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.
73
Which is Faster?Getting to the 753rd item in a linked
list or an array?
Linked Lists vs. ArraysWinner: Array
We can get to any item in an array in 1 step. We have to pass thru 752 other
nodes to reach the 753rd 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 ListWe 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 ListOnce 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: ArrayLet’s face it – arrays are easier to use. So only use a linked list if you
really have to!
74
Class ChallengeWrite 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
newnodeDataNextPrev
Dave
exists
nullptrnullptr