04 Linkedlist
04 Linkedlist
Outline
– Introduction to linked list
– Singly linked list
– Circular linked list
– Doubly linked list
– Circular doubly linked list
Linked List Basics
Linked lists and arrays are similar since they both
store collections of data.
The array's features all follow from its strategy of
allocating sequentially its elements.
Linked lists use an entirely different strategy: linked
lists allocate memory for each element separately
and only when necessary.
Linked List Basics
• Linked lists are used to store a collection of
information (like arrays)
• A linked list is made of nodes that are pointing to
each other
• We only know the address of the first node (head)
• Other nodes are reached by following the “next”
pointers
• The last node points to NULL
4
Linked Lists
head
a b c d
head = NULL;
head
Linked List Basics
• Each node has (at least) two fields:
– Data
– Pointer to the next node
data ptr
7
Linked List vs. Array
• In a linked list, nodes are not necessarily
contiguous in memory (each node is allocated with
a separate “new” call)
• Elements of array are contiguous
Array
10
Linked List Implementation
• A linked list node can be represented as follows
11
Linked List Node Implementation
• We can add a constructor to simplify creating a
new node:
template <class T>
class Node {
public:
Node(const T& e = T(), Node *n = NULL) :
element(e), next(n) { }
T element;
Node *next;
};
12
Linked List Implementation
• A linked list can be defined as follows
• Note that the constructor creates an empty list by
making the head point to NULL
13
Basic Linked List Operations
• Insert a node
• Delete a node
• List Traversal
• Searching a node
• Is Empty
Linked List Operations
• isEmpty(): returns true if the list is empty, false
otherwise
15
Linked List Operations
• isEmpty(): returns true if the list is empty, false
otherwise
16
Linked List Operations
• first(): returns the first node of the list
17
Linked List Operations
18
Insertion in a linked list
• insert(const T& data, Node<T>* p): inserts a
new element containing data after node p
a b
… …
p
x
newNode
Insertion
• insert(const T& data, Node<T>* p): inserts a new
element containing data after node p
• We consider two cases:
1. Insertion to the middle
2. Insertion before the head (or to an empty list)
• In the second case, we have to update the head pointer as
well
20
Linked List Operations: insert
• insert(const T& data, Node<T>* p): inserts a
new element containing data after node p
21
Linked List Operations
if (p != NULL) { // case 1
Node<T>* newNode = new Node<T>(data, p->next);
p->next = newNode;
}
else { // case 2
Node<T>* newNode = new Node<T>(data, head);
head = newNode;
}
}
22
Linked List Operations
• To avoid this if-check at every insertion, we can add a
dummy head to our list (also useful for deletion)
• This dummy head will be our zeroth node and its next
pointer will point to the actual head (first node)
dummyHead
X a b c
First node 23
Linked List Operations
• An empty list will look like this (the contents of the
node is irrelevant):
dummyHead
next
24
Linked List Operations
• Now, insertion code is simplified:
template <class T>
void List<T>::insert(const T& data, Node<T>* p) {
25
Linked List Operations
• We must make some changes to support the dummy
head version:
26
Linked List Operations
template <class T>
class List { “Note that if we don't
private:
have a constant first()
Node<T> *dummyHead;
public:
function, we cannot
Node<T>* zeroth() { make isEmpty const as
return dummyHead; well”
}
Node<T>* first() {
return dummyHead->next;
}
const Node<T>* first() const {
return dummyHead->next;
}
bool isEmpty() const {first() == NULL;}
27
};
Searching for an Element
• To find an element, we must loop through all elements
until we find the element or we reach the end:
while (p) {
if (p->element == data)
return p;
p = p->next;
}
return NULL;
} 29
Removing a node from a linked list
tmp
a x b
… …
p
Removing an Element
• To remove a node containing an element, we must find
the previous node of that node. So we need a
findPrevious function
template <class T>
class List {
private:
Node<T> *dummyHead;
public:
...
Node<T>* findPrevious(const T& data);
...
};
31
Finding Previous Node
template <class T>
Node<T>* List<T>::findPrevious(const T& data) {
Node<T>* p = zeroth();
while (p->next) {
if (p->next->element == data)
return p;
p = p->next;
}
return NULL;
}
32
Removing an Element
• Now we can implement the remove function:
33
Removing an Element
• Note that, because we have a dummy head, removal of
an element is simplified as well
template <class T>
void List<T>::remove(const T& data) {
Node<T>* p = findPrevious(data);
if (p) {
Node<T>* tmp = p->next;
p->next = tmp->next;
delete tmp;
}
}
34
Printing All Elements (Traversal)
• List traversal is straightforward:
35
Printing All Elements (Traversal)
• List traversal is straightforward:
“Again, the constant
first() function allows
template <class T> print() to be const as
void List<T>::print() const well”
{
const Node<T>* p = first();
while(p) {
std::cout << p->element << std::endl;
p = p->next;
}
}
36
Removing All Elements
• We can make the list empty by deleting all nodes
(except the dummy head)
template <class T>
class List {
private:
Node<T> *dummyHead;
public:
...
void makeEmpty();
...
};
37
Removing All Elements
template <class T>
void List<T>::makeEmpty()
{
while(!isEmpty()) {
remove(first()->element());
}
}
38
Destructor
• We must release allocated memory in the destructor
39
Destructor
template <class T>
List<T>::~List()
{
makeEmpty();
delete dummyHead;
}
40
Assignment Operator
• As the list has pointer members, we must provide an
assignment operator
template <class T>
class List {
private:
Node<T> *dummyHead;
public:
...
List& operator=(const List& rhs);
...
};
41
Assignment Operator
template <class T>
List<T>& List<T>::operator=(const List& rhs)
{
if (this != &rhs) {
makeEmpty();
Node<T>* r = rhs.first();
Node<T>* p = zeroth();
while (r) {
insert(r->element, p);
r = r->next;
p = p->next;
}
}
return *this;
}
42
Copy Constructor
• Finally, we must implement the copy constructor
43
Copy Constructor
template <class T>
List<T>::List(const List& rhs)
{
dummyHead = new Node<T>(T(), NULL);
44
Testing the Linked List Class
• Let's check if our implementation works by
implementing a test driver file
int main() {
List<int> list;
list.insert(0, list.zeroth());
Node<int>* p = list.first();
46
Testing the Linked List Class
List<int> list2 = list;
cout << "printing copy constructed list" << endl;
list2.print();
List<int> list3;
list3 = list;
cout << "printing assigned list" << endl;
list3.print();
list.makeEmpty();
cout << "printing emptied list" << endl;
list.print();
47
Testing the Linked List Class
for (int i = 0; i <= 10; ++i) {
if (i % 2 == 0) {
if (list2.find(i) == NULL)
cout << "could not find element " << i << endl;
}
else {
if (list2.find(i) != NULL)
cout << "found element " << i << endl;
}
}
48
Variations of Linked Lists
• The linked list that we studied so far is called singly
linked list
• Other types of linked lists:
– Circular linked list
– Doubly linked list
– Circular doubly linked list
• Each type of linked list may be suitable for a different
kind of application
• They may also use a dummy head for simplifying
insertions and deletions
49
Circular Linked Lists
• Last node references the first node
• Every node has a successor
• No node in a circular linked list contains NULL
50
Doubly Linked Lists
pHead
a b c
Advantage
• Convenient to traverse the list backwards
Disadvantage
• Increase in space requirements due to storing two pointers
instead of one
51
Deletion
oldNode
oldNode->prev->next = oldNode->next;
oldNode->next->prev = oldNode->prev;
delete oldNode;
Insertion
current
54
Circular Doubly Linked Lists
55
Linked List Problems
• Reversing a list
• Create 3 nodes, currNode, PrevNode and nextNode
• Initialize them as currNode=head; nextNode=null;prevNode = null;
• Now keep reversing the pointers one by one till currNode!=null