Data Structures: Linked Lists (Part 3)

Download as pptx, pdf, or txt
Download as pptx, pdf, or txt
You are on page 1of 32

Data Structures

CT077-3-2- DSTR and Version VD1

Linked Lists
(Part 3)
Topic Structure

• Linked list improvement


• Other types of linked list
- Doubly linked list
- Circular linked list

CT077-3-2-DSTR Linked List (Part 3) Slides 2 of 32


Objectives

By the end of this lesson, you will be able to:


• Improve implementation of some operations on
linked lists
• Create an abstract linked list type using C++
templates
• Implement reversed traversal of a linked list
using loops and recursion
• Understand the concept of Doubly Linked Lists,
and Circular Linked Lists

CT077-3-2-DSTR Linked List (Part 3) Slides 3 of 32


Key Terms

• Tail
• Reverse traversal
• Doubly linked list
• Circular linked list

CT077-3-2-DSTR Linked List (Part 3) Slides 4 of 32


Improving Operations

• Inserting a new item at beginning of a linked list is


fast (no traversal required)

X X
lastNode

newNode newNode

• While inserting at the end of the list required us to


traverse the whole list to reach the last element

CT077-3-2-DSTR Linked List (Part 3) Slides 5 of 32


Improving Operations

• If in a program there is a need to frequently insert


items at end of the list, it’s worth to change the data
structure to allow more efficient implementation
class LinkedList {
public:
NodeType* head;
NodeType* tail;
int size;
// .. the other implemented methods
};

CT077-3-2-DSTR Linked List (Part 3) Slides 6 of 32


Insert at the End

• Now insert at end can be implemented efficiently


void insertAtEnd(int value){
NodeType * newNode= new NodeType;
newNode->info = value;
newNode->link = NULL;
if (head == NULL)//inserting to an empty list
head = tail = newNode;
else{
tail->link = newNode;
tail = newNode;
}
size++;
}

X
5
newNode

CT077-3-2-DSTR Linked List (Part 3) Slides 7 of 32


Change Previous Implementations

• Note however that other implemented operations


may have to be changed as well to consider the
new tail field and properly update it if necessary
– Constructor and destructor
– clear()
– insertAtBeginning(…)
– deleteFirst(), deleteLast(), and deleteItemAt(…)
– insertItemAt(…)
• no change necessary if it depends on inserting at
beginning and end methods

CT077-3-2-DSTR Linked List (Part 3) Slides 8 of 32


Necessary Modifications

• For example, the constructor will be modified to:


LinkedList(){
this->size = 0;
• Inserting a new item at this->head= NULL;
this->tail= NULL;
the beginning of a list }
will affect the tail ONLY void insertAtBeginning(int value){
if the list was empty NodeType * newNode= new NodeType;
newNode->info = value;
newNode->link = head;
head = newNode;
size++;
if(tail == NULL)
tail = newNode;
}

CT077-3-2-DSTR Linked List (Part 3) Slides 9 of 32


Necessary Modifications

• deleteFirst() should also be changed


• Deleting first item in the list will affect tail ONLY if
the list becomes empty after deletion
void deleteFirst(){
if(size > 0){//else, may want to give a message or error
NodeType * toBeDeleted = head;
head = head->link;
delete toBeDeleted;
size--;

if(head == NULL)
tail = NULL;
}
}

CT077-3-2-DSTR Linked List (Part 3) Slides 10 of 32


deleteLast efficiency improvement?

• Note that deleteLast() will not benefit from the tail


pointer to improve efficiency, since we still need a
pointer to the item before the last one

beforeLast

• This can be solved later when doubly linked lists


are introduced

CT077-3-2-DSTR Linked List (Part 3) Slides 11 of 32


Reverse Traversal of a Linked List

• What if we needed to traverse list elements in


reversed order (for printing, or other purposes)?

• For array lists, this job was easy !


//..since we have fast access to any array item directly
for(int i= arrSize-1; i>=0; i--)
cout << arr [ i ] << " ";

CT077-3-2-DSTR Linked List (Part 3) Slides 12 of 32


Reverse Print of a Linked List

• One way is to repeatedly traverse the list up to a


certain decreasing limit (and print item at that limit)

Note that this


solution is not
current efficient, since
the nested loop
means we are
class LinkedList { traversing most
void printReverse(){ of list elements
for(int limit= size-1; limit>=0; limit--){ multiple times
NodeType * current = head;
for(int i= 0; i<limit; i++) Equivalent to:
current = current->link; cout<<getItemAt(limit)
cout << current->info << " ";
}
}
};
CT077-3-2-DSTR Linked List (Part 3) Slides 13 of 32
Reverse Print – Using Recursion

• Using a recursive function, a more elegant and


efficient (although not best) way can be implemented

class LinkedList {
void printReverse(){
printRecursive(head);
}
void printRecursive(NodeType * current){
if(current != NULL) {//stop recursion if we reach list end
printRecursive(current->link); //print remaining items first
cout << current->info << " "; //then print current item
}
}
};
CT077-3-2-DSTR Linked List (Part 3) Slides 14 of 32
p
r
p
i
rn How is Recursion Executed?
it
n
R
te
R
c
e
u
cr
void printRecursive(NodeType
p * current){
u
s
if(current
ri != NULL){
ri nt
printRecursive(current->link);
sv void printReverse(){ cout s << current->info << " ";
ie printRecursive(head); 6
3
7
} 5,
4,
6,
v }
e } r
o
et
f prints 45, done. return to caller
o u
fn r
n
o
n to
d
u c
e
l al
l7 le recursion ends,
4
6
3
r execution returns to
5
4
6
n
, caller
o
d
c
e
a
,l
l
n
s
o

CT077-3-2-DSTR Linked List (Part 3) Slides 15 of 32
Doubly Linked Lists

• Some applications need to efficiently access adjacent


nodes, whether before or after a certain item
• This allows list traversal in any direction
– Re-implement printReverse()

template <class T> template <class T>


class DoublyNodeType { class DoublyLinkedList {
public: public:
T info; DoublyNodeType<T> * head;
DoublyNodeType<T> * next; DoublyNodeType<T> * tail;
DoublyNodeType<T> * prev; int size;
}; };
CT077-3-2-DSTR Linked List (Part 3) Slides 16 of 32
Implementation Updates

• Operations need to be updated. For example:


void insertAtEnd(T value){
DoublyNodeType<T> * newNode= new DoublyNodeType<T>;
newNode->info = value;
newNode->next = NULL;
newNode->prev = tail;
tail = newNode;
if (head == NULL)//inserting to an empty list
head = newNode;
else
newNode->prev->next = newNode;
size++;
}

X
X
4
newNode

CT077-3-2-DSTR Linked List (Part 3) Slides 17 of 32


Implementation Updates

• Inserting at the beginning becomes


void insertAtBeginning(T value){
DoublyNodeType<T> * newNode= new DoublyNodeType<T>;
newNode->info = value;
newNode->next = head;
newNode->prev = NULL;
head = newNode;
if (tail == NULL)//inserting to an empty list
tail = newNode;
else
newNode->next->prev = newNode;
size++;
}

X
X

4 newNode

CT077-3-2-DSTR Linked List (Part 3) Slides 18 of 32


Implementation Updates
• Inserting at an arbitrary index (e.g. index=2)
void insertItemAt(T value, int index){
if(index <= size)
if(index == 0)
insertAtBeginning(value);
else if(index == size)
insertAtEnd(value);
else{ //the general case
DoublyNodeType<T> * newNode= new DoublyNodeType<T>;
newNode->info = value;
DoublyNodeType<T> * beforeThis = head; Can be optimized if (index > size/2)
for(int i=0; i<index; i++) by starting from the tail and moving
backwards to find beforeThis.
beforeThis = beforeThis->next; More on this later
//code continues
}
}

beforeThis newNode
CT077-3-2-DSTR Linked List (Part 3) Slides 19 of 32
Inserting at Arbitrary Index
//code continues
newNode->next = beforeThis;
newNode->prev = beforeThis->prev;
beforeThis->prev = newNode;
newNode->prev->next = newNode;
size++;

newNode beforeThis

beforeThis

newNode

CT077-3-2-DSTR Linked List (Part 3) Slides 20 of 32


Deleting a List Item

• Code for deleting a node from beginning, end, or arbitrary


index must also be updated
• For example, deleting item at index 1
//locate the
node //to be
deleted
toDelete

//if prev and next nodes exist


teDelete->prev->next =
toDelete->next;
toDelete->next->prev =
toDelete->prev;

//update head/tail if necessary


delete toDelete;
size--;

CT077-3-2-DSTR Linked List (Part 3) Slides 21 of 32


Notes on Doubly Linked Lists

• The main advantage of Doubly Linked Lists is to


efficiently traverse list nodes in both directions
– Applications: to implement back and forward buttons in a
web browser, undo and redo actions, etc.

• We can also use two-way traversal to reduce time


required to reach an item at a given index
– if index <= size/2, start from head and move forward
– otherwise, start at tail and move backward
– on average, would make insertItemAt(), deleteItemAt(),
getItemAt() and setItemAt() 2 times faster
CT077-3-2-DSTR Linked List (Part 3) Slides 22 of 32
Circular Linked Lists

• Some problems are inherently circular (squares on a


Monopoly board), and many solutions can be solved
more naturally using a circular data structure (round
robin scheduling, players taking turns, playing video
and sound files in “looping” mode, etc.)
• A linked list is made circular if its end points to its
beginning, i.e. the last node points to the first one
– Circular traversal is made more natural, rather than always
testing if we have reached to the end and starting over

CT077-3-2-DSTR Linked List (Part 3) Slides 23 of 32


Circular Linked Lists

• Data representation for a Circular Linked List is not


different from the normal Linked List
– The concept is just not to store NULL at the link field of last
node, and respect that in implementing all operations
– Often, tail pointer is only used instead of head

template <class T>


class CircularLinkedList {
public:
NodeType<T> * head;
NodeType<T> * tail;
int size;
};
CT077-3-2-DSTR Linked List (Part 3) Slides 24 of 32
Circular Doubly Linked Lists

• Circularity can be also applied to a doubly linked list,


making it Circular Doubly Linked List

template <class T>


class CircularDoublyLinkedList {
public:
DoublyNodeType<T> * tail;
int size;
};
CT077-3-2-DSTR Linked List (Part 3) Slides 25 of 32
Circular Linked List Operations

• Operations must be updated accordingly, e.g.


template <class T>
class CircularLinkedList {
public:
NodeType<T> * tail;
int size;

void print(){
if(tail != NULL){
NodeType<T> * current = tail->link;
do{
cout << current->info << " ";
current = current->link;
}while (current != tail->link);
cout << endl;
}
}
};

CT077-3-2-DSTR
current
Linked List (Part 3) Slides 26 of 32
Circular Linked List Operations

• Insert at beginning becomes


void
voidinsertAtBeginning(T
print(){ value){
NodeType<T> * newNode=
if(tail != NULL){new NodeType<T>;
newNode->info
NodeType =* value;
current = tail->next;
if (tail
do{ == NULL){//inserting to an empty list
tail =cout
newNode;
<< current->info << " ";
tail->link = tail;
current = current->next;
} }while (current != tail->next);
else{ cout << endl;
newNode
newNode->link
} = tail->link;
tail->link
} = newNode;
}
size++;
}

X
4
newNode
CT077-3-2-DSTR Linked List (Part 3) Slides 27 of 32
Array or Linked List?
• Finally, should we always use linked lists?
• Decision depends on what operations will be used
most frequently, and which factors (speed/memory)
are more critical. Following are some hints:
– Number of elements is known: use array
– Dynamic addition and expansion: linked list
– Deletion at any position: linked list
– Need lots of random access: use array
– Searching and items are unsorted: both same
– Searching and items are sorted: array (binary search)
– Sorting using bubble sort: both same
– Sorting using other methods: depends on the method
CT077-3-2-DSTR Linked List (Part 3) Slides 28 of 32
Summary of Main Teaching

• Linked list improvement


• Other types of linked list
- Doubly linked list
- Circular linked list

CT077-3-2-DSTR Linked List (Part 3) Slides 29 of 32


Quick review

• Explain the advantages and disadvantages of


using recursive function and doubly linked list for
reverse traversal in linked list.
• Give example of applications that are better
implemented using array instead of linked list
and vice versa.

CT077-3-2-DSTR Linked List (Part 3) Slides 30 of 32


CT077-3-2-DSTR Linked List (Part 3) Slides 31 of 32
What will we cover next

• Stack
– Applications
– Operations
– Implementation

CT077-3-2-DSTR Linked List (Part 3) Slides 32 of 32

You might also like