3.05.linked Lists
3.05.linked Lists
3.05.linked Lists
ece.uwaterloo.ca
dwharder@alumni.uwaterloo.ca
Outline
Definition
1 Linked Lists
A Node class must store the data and a reference to the next node
(also a pointer)
Linked Lists
5
1 Node Class
class Node {
public:
Node( int = 0, Node * = nullptr );
private:
int node_value;
Node *next_node;
};
Linked Lists
6
Accessors
The two member functions are accessors which simply return the
node_value and the next_node member variables, respectively
Accessors
Possible solutions:
Member Member
Variables Functions
Vary capitalization next_node Next_node() or NextNode()
Prefix with “get” next_node get_next_node() / getNextNode()
Use an underscore next_node_ next_node()
Different names next_node next()
Because each node in a linked lists refers to the next, the linked list
class need only link to the first node in the list
private:
Node *list_head;
// ...
};
Linked Lists
10
Structure
Structure
A linked list uses linked allocation, and therefore each node may
appear anywhere in memory
Also the memory required for each node equals the memory
required by the member variables
– 4 bytes for the linked list (a pointer)
– 8 bytes for each node (an int and a pointer)
• We are assuming a 32-bit machine
Linked Lists
12
Structure
Structure
Structure
Structure
list_
Operations
Operations
Operations
All these operations relate to the first node of the linked list
Linked Lists
Linked Lists
// Accessors
bool empty() const;
int size() const;
int front() const;
Node *begin() const;
Node *end() const;
int count( int ) const;
// Mutators
void push_front( int );
int pop_front();
int erase( int );
private:
Node *list_head;
};
Linked Lists
21
Linked Lists
The Constructor
List::List():list_head( nullptr ) {
// empty constructor
}
We will always ensure that when a linked list is empty, the list head
is assigned nullptr
Linked Lists
23
Allocation
Dynamically
The statement
List *pls = new List();
requests sufficient memory from the OS to store an instance of the class
– In both cases, the memory is allocated and then the constructor is called
Linked Lists
24
Static Allocation
Example:
int f() {
List ls; // ls is declared as a local variable on the stack
ls.push_front( 3 );
cout << ls.front() << endl;
return 0;
}
Linked Lists
25
Dynamic Allocation
Example:
pls->push_front( n );
cout << pls->front() << endl;
return pls;
}
Linked Lists
26
Static Allocation
What if I do?
List *f() {
List ls; // ls is declared as a local variable on the stack
ls.push_front( 3 );
cout << ls.front() << endl;
return &ls;
}
Linked Lists
27
Better yet:
bool List::empty() const {
return ( list_head == nullptr );
}
Linked Lists
28
This will always work: if the list is empty, it will return nullptr
Linked Lists
29
To get the first value in the linked list, we must access the node to
which the list_head is pointing
Because we have a pointer, we must use the -> operator to call the
member function:
int List::front() const {
return begin()->value();
}
Linked Lists
31
We define a class
class underflow {
// empty
};
and then we throw an instance of this class:
throw underflow();
Linked Lists
33
return begin()->value();
}
Linked Lists
34
return list_head->node_value;
}
?
Two benefits:
– More readable
– If the implementation changes we do nothing
Linked Lists
35
int pop_front()
Graphically, given:
we want:
Linked Lists
44
int pop_front()
Easy enough:
int List::pop_front() {
int e = front();
list_head = begin()->next();
return e;
}
int pop_front()
int List::pop_front() {
if ( empty() ) {
throw underflow();
}
int e = front();
delete begin();
list_head = begin()->next();
return e;
}
Linked Lists
46
int pop_front()
int List::pop_front() {
if ( empty() ) {
throw underflow();
}
int e = front();
delete begin();
list_head = begin()->next();
return e;
}
Linked Lists
47
int pop_front()
int List::pop_front() {
if ( empty() ) {
throw underflow();
}
int e = front();
delete begin();
list_head = begin()->next();
return e;
}
Linked Lists
48
int pop_front()
int List::pop_front() {
if ( empty() ) {
throw underflow();
}
int e = front();
delete begin();
list_head = begin()->next();
return e;
}
Linked Lists
49
int pop_front()
http://xkcd.com/379/
Linked Lists
50
int pop_front()
int List::pop_front() {
if ( empty() ) {
throw underflow();
}
int e = front();
Node *ptr = list_head;
list_head = list_head->next();
delete ptr;
return e;
}
Linked Lists
51
int pop_front()
int List::pop_front() {
if ( empty() ) {
throw underflow();
}
int e = front();
list_head = begin()->next();
delete ptr;
return e;
}
Linked Lists
52
int pop_front()
int List::pop_front() {
if ( empty() ) {
throw underflow();
}
int e = front();
list_head = begin()->next();
delete ptr;
return e;
}
Linked Lists
53
int pop_front()
int List::pop_front() {
if ( empty() ) {
throw underflow();
}
int e = front();
list_head = begin()->next();
delete ptr;
return e;
}
Linked Lists
54
int pop_front()
int List::pop_front() {
if ( empty() ) {
throw underflow();
}
int e = front();
list_head = begin()->next();
delete ptr;
return e;
}
Linked Lists
55
Thus, we have:
Analogously:
ptr != nullptr and thus we evaluate the body of the loop and
then set ptr to the next pointer of the node it is pointing to
Linked Lists
60
ptr != nullptr and thus we evaluate the loop and increment the
pointer
ptr != nullptr and thus we evaluate the loop and increment the
pointer
Also, in the loop, we can access the next node in the list by using
ptr->next()
Linked Lists
62
ptr != nullptr and thus we evaluate the loop and increment the
pointer
Here, we check and find ptr != nullptr is false, and thus we exit
the loop
Because the variable ptr was declared inside the loop, we can no
longer access it
Linked Lists
64
The implementation:
return node_count;
}
Linked Lists
66
Notice that the erase function must modify the member variables of
the node prior to the node being removed
Possible solutions:
– Friends
– Nested classes
– Inner classes
Linked Lists
68
C++ Friends
class A {
private:
int class_size;
// ... declaration ...
friend class B;
};
C++ Friends
For example, if the Node class was one class, and the List class was
a friend of the Node class, List::erase could modify nodes:
if ( some condition ) {
ptr->next_node = ptr->next()->next();
// ...
++node_count;
}
}
return node_count;
}
Linked Lists
70
Nested Classes
In C++, you can nest one class inside another, which is what we do:
class Outer {
private:
class Nested {
private:
int node_value;
public:
int get() const;
void set( int );
};
Nested stored;
public:
int get() const;
void set( int );
};
Linked Lists
71
Nested Classes
stored.set( n );
}
Linked Lists
72
Inner Classes
If Node was an inner class of List, the node could determine its
position with the list (not possible in C++):
int List::Node::position() const {
int posn = 1;
return posn;
}
Linked Lists
73
Destructor
Destructor
Destructor
The destructor has to delete any memory which had been allocated
but has not yet been deallocated
Destructor
Is this efficient?
Making Copies
Initially, it may appear yes, but we now have to look at how C++
copies objects during:
– Passing by value (making a copy), and
– Assignment
Linked Lists
78
Pass by Value
int main() {
int counter = 0;
increment( counter );
Pass by Reference
#include <iostream>
int main() {
int counter = 0;
increment( counter );
#include <stdio.h>
int main() {
int counter = 0;
increment( &counter );
Modifying Arguments
while ( !list.empty() ) {
tmp.push_front( ls.pop_front() );
}
Modifying Arguments
return sum/count;
}
Note: this reveals a weakness in our model—we will discuss iterators later…
Linked Lists
83
Modifying Arguments
Modifying Arguments
First, the list prim is created and three values are pushed onto it
int main() {
List prim;
send_copy( prim );
return 0;
}
Linked Lists
85
Modifying Arguments
int main() {
List prim;
send_copy( prim );
return 0;
}
Linked Lists
86
Modifying Arguments
int main() {
List prim;
send_copy( prim );
return 0;
}
Linked Lists
87
Modifying Arguments
int main() {
List prim;
send_copy( prim );
return 0;
}
Linked Lists
88
Modifying Arguments
int main() {
List prim;
send_copy( prim );
return 0;
}
Linked Lists
89
Modifying Arguments
int main() {
List prim;
send_copy( prim );
return 0;
}
Linked Lists
90
Modifying Arguments
int main() {
List prim;
send_copy( prim );
return 0;
}
Linked Lists
91
Modifying Arguments
int main() {
List prim;
send_copy( prim );
return 0;
}
Linked Lists
92
Copy Constructor
You can modify how copies are made by defining a copy constructor
– The default copy constructor simply copies the member variables
– In this case, this is not what we want
Copy Constructor
Copy Constructor
to
Linked Lists
95
Copy Constructor
Copy Constructor
– In Project 1, you will define and use the member variable list_tail
Linked Lists
97
Copy Constructor
}
Linked Lists
98
Copy Constructor
}
Linked Lists
99
Copy Constructor
push_front( list.front() );
}
Linked Lists
100
Copy Constructor
push_front( list.front() );
}
Note, we will need to loop through the list… How about a for loop?
Linked Lists
101
Copy Constructor
push_front( list.front() );
for (
Node *original = list.begin()->next(), *copy = begin();
original != list.end();
original = original->next(), copy = copy->next()
) {
copy->next_node = new Node( original->value(), nullptr );
}
}
Linked Lists
102
Copy Constructor
push_front( list.front() );
for (
Node *original = list.begin()->next(), *copy = begin();
original != list.end();
original = original->next(), copy = copy->next()
) {
copy->next_node = new Node( original->value(), nullptr );
}
}
Linked Lists
103
Copy Constructor
push_front( list.front() );
for (
Node *original = list.begin()->next(), *copy = begin();
original != list.end();
original = original->next(), copy = copy->next()
) {
copy->next_node = new Node( original->value(), nullptr );
}
}
Linked Lists
104
Assignment
lst1.push_front( 35 );
lst1.push_front( 18 );
lst2.push_front( 94 );
lst2.push_front( 72 );
Linked Lists
105
Assignment
Consider an assignment:
lst2 = lst1;
Assignment
It is equivalent to writing:
lst2.list_head = lst1.list_head;
Graphically:
Linked Lists
107
Assignment
Assignment
Now, the second list lst2 is pointing to memory which has been
deallocated...
Assignment
– We need to erase the content of lst2 and copy over the nodes in lst1
Linked Lists
110
Assignment
Return by Value
return ls;
}
and call
List vec = initialize( 3, 6 );
Linked Lists
112
Return by Value
return ls;
}
Linked Lists
113
Return by Value
return ls;
}
Linked Lists
114
Return by Value
return ls;
}
Linked Lists
115
Return by Value
return ls;
}
Linked Lists
116
Return by Value
return ls;
}
Linked Lists
117
Return by Value
return ls;
}
Linked Lists
118
Return by Value
return ls;
}
Linked Lists
119
Move Constructors
Assignment
Assignment
We will swap all the values of the member variables between the
left- and right-hand sides
– rhs is already a copy, so we swap all member variables of it and *this
return *this;
}
Linked Lists
122
Assignment
return *this;
}
Linked Lists
123
Assignment
Assignment
Assignment
Assignment
Assignment
Assignment
Can we do better?
– This idea of copy and swap is highly visible in the literature
– If the copy constructor is written correctly, it will be fast
– Is it always the most efficient?
Assignment
Can we do better?
– This idea of copy and swap is highly visible in the literature
– If the copy constructor is written correctly, it will be fast
– Is it always the most efficient?
Assignment
Assignment
if ( rhs.empty() ) {
while ( !empty() ) {
pop_front();
}
return *this;
}
Linked Lists
132
Assignment
if ( empty() ) {
list_head = new Node( rhs.front() );
} else {
begin()->node_value = rhs.front();
}
while ( rhs_node != 0 ) {
if ( this_node->next() == 0 ) {
this_node->next_node = new Node( rhs_node->value() );
this_node = this_node->next();
} else {
this_node->next();
this_node->node_value = rhs_node->value();
}
rhs_node = rhs_node->next();
}
Linked Lists
133
Assignment
while ( this_node->next() != 0 ) {
Node *tmp = this_node->next();
this_node->next_node = this_node->next()->next();
delete tmp;
}
return *this;
}
Linked Lists
134
Move Assignment
list_head = rhs.begin();
rhs.list_head = 0;
return *this;
}
Linked Lists
135
Linked Lists
public:
// Constructors and destructors
List();
List( List const & ); // Accessors
List( List && ); bool empty() const;
~List(); int size() const;
int front() const;
// Assignment operators Node *begin() const;
Node *end() const;
List &operator = ( List );
int count( int ) const;
List &operator = ( List && );
// Mutators
void push_front( int );
int pop_front();
int erase( int );
};
Linked Lists
136
Linked Lists
*
these become Q(1) if we have a tail pointer
Linked Lists
137
Efficient Allocation
Efficient Allocation
Efficient Allocation
Efficient Allocation
Efficient Allocation
Efficient Allocation
Efficient Allocation
Efficient Allocation
Efficient Allocation
Summary
References
Donald E. Knuth, The Art of Computer Programming, Volume 3: Sorting and Searching, 2nd
Ed., Addison Wesley, 1998, §5.4, pp.248-379.
Wikipedia, https://en.wikipedia.org/wiki/Linked_list
http://stackoverflow.com/error?aspxerrorpath=/questions/8848363/rvalue-reference-with-assignement-operator
These slides are provided for the ECE 250 Algorithms and Data Structures course. The
material in it reflects Douglas W. Harder’s best judgment in light of the information available to
him at the time of preparation. Any reliance on these course slides by any party for any other
purpose are the responsibility of such parties. Douglas W. Harder accepts no responsibility for
damages, if any, suffered by any party as a result of decisions made or actions based on these
course slides for any other purpose than that for which it was intended.