0% found this document useful (0 votes)
10 views47 pages

Lecture 8 - LinkedList - BSSE - Evening - 3B

Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PPTX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
10 views47 pages

Lecture 8 - LinkedList - BSSE - Evening - 3B

Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PPTX, PDF, TXT or read online on Scribd
You are on page 1/ 47

DATA STRUCTURES SADIA

AND ALGORITHMS ZAR


LIST DATA STRUCTURE
LIST – DATA STRUCTURE
•Lists are such data structures which are used to maintain elements
of same data type in a linear/sequential way.
•A list or series is an abstract data type that implements an ordered
collection of values, where the same value can occur more than one
time.
•Static list structures allow only inspection and enumeration of the
values of its elements.
•A dynamic list may allow items to be inserted, replaced, or deleted
during the list's existence. Size of dynamic lists can be changed
during program execution.
LIST – DATA STRUCTURE
•The list data structure typically has two very distinctive implementations —
array list and linked list.

Array List
•Let’s examine the first and maybe the most commonly used implementation of a list
— the array list.
•The array list is basically a self-resizing array or, in other words, a dynamic array.
•This data structure behaves exactly like an ordinary array but with an additional
capacity property that invokes a size expansion every time it’s exceeded
ARRAY LIST – DATA
STRUCTURE
•This means that this data structure can grow as much as it needs — compared to the
classical static array which cannot because it has a predefined maximum size.
•This brings us to the advantages and disadvantages of an array list.
•A wonderful thing about array lists and arrays as a whole is that since their elements
are sequential in memory, they can greatly benefit from the CPU cache. This
significantly improves the speed with which you can operate over the data structure.
•A not so wonderful thing is that the resize operation can sometimes fail due to
problems that we’ll mention further on.
•On top of that, the array list tends to sometimes use up more space than it actually
needs to.
ARRAY LIST – DATA
STRUCTURE
Usage
•The need for this data structure is most obvious when you have the urge to store
items without knowing how much space you’ll need in advance.
•Also, when you often have the need to add or get elements from a data structure, the
array list would suit well because those operations are fast and efficient.
•It’s not that good of a choice for a data structure if there’s a need to often remove or
insert elements.
Complexity
add(element) → O(1)
remove(element) → O(n)
insert(element, index) → O(n)
get(index) → O(1)
ARRAY LIST – DATA
STRUCTURE
Implementation
template<typename T>
class ArrayList {
public:
ArrayList();
private:
explicit ArrayList(int capacity); void resize();
void add(const T& item); T* _array;
void insert(const T& item, int index); int _capacity;
void remove(const T& item); int _size;
void removeAt(int index); };
T get(int index) const;
bool contains(const T& item) const;
int size() const;
void print() const;
ARRAY LIST – DATA
STRUCTURE
Implementation - Constructors
template<typename T>
ArrayList<T>::ArrayList(const int capacity) :
_array(nullptr),
_capacity(capacity),
_size(0) {
// Initialize all class variables.
_array = new T[_capacity];
}

template<typename T>
ArrayList<T>::ArrayList() : ArrayList(8) {
// If no capacity is specified, use 8 as a default initial value
// and delegate the initialization to the constructor above.
}
ARRAY LIST – DATA
STRUCTURE
Add operation
This operation is fast most of the time, and when it’s not, it’s in the cases when the
array list has to increase its capacity.
To see how the resizing works, let’s consider this array list.

An array list with a size of 4 and a capacity of 4

The array list from the picture above has reached its capacity, and on the next add
operation, the resizing mechanism will be triggered.

An array list with a size of 5 and a capacity of 8


ARRAY LIST – DATA
STRUCTURE
This is what you’ll get after the capacity-overflowing add operation — the array list
with double the capacity.
Let’s see this in code.
/**
* Add a new item to the data structure.
* @tparam T type
* @param item item to add
*/
template<typename T>
void ArrayList<T>::add(const T& item) {
// Check if there's a need to make additional space.
if (_size >= _capacity) {
resize();
}

// Add the item to the data structure and increase the size by 1.
_array[_size++] = item;
}
ARRAY LIST – DATA
STRUCTURE
RemoveAt operation
•Element removal in the array list is a rather slow operation.
•This is because when you remove an element, in order to preserve the contiguous
nature of the internal static-array data structure, you have to shift all elements which
come after the removed one with one position to the left.

An internal array list remove-element operation


ARRAY LIST – DATA
STRUCTURE
/**
* Remove an element from a specified index.
* @tparam T type
* @param index index of an element
*/
template<typename T>
void ArrayList<T>::removeAt(const int index) {
if (index < 0 || index >= _size) {
// If the index is out of bounds, throw an exception.
throw std::invalid_argument("Index out of bounds");
}else {
// Shift all elements from the provided index up to the last one
// with one position to the left.
for (int i = index; i < _size - 1; ++i) {
_array[i] = _array[i + 1];
}
// Reduce the size of the data structure.
--_size;
}}
ARRAY LIST – DATA
STRUCTURE
Remove operation
This operation is pretty much summed up by the removeAt operation.
/**
* Remove all elements from the data structure which match
* the item provided.
*
* @tparam T type
* @param item item to be removed
*/
template<typename T>
void ArrayList<T>::remove(const T& item) {
// Iterate through all elements and remove those
// who are equal to the one provided.
for (int i = 0; i < _size; ++i) {
if (_array[i] == item) {
removeAt(i);

// Reduce the iterator variable in order


// to stay at the same place after an element
// remove operation.
--i;
}}}
ARRAY LIST – DATA
STRUCTURE
Insert operation
The insert operation is similar to the removeAt operation but with shifting elements
in the opposite direction from the specified index in order to make space for the new
element.
/**
* Insert an element to the data structure at a specified index.
*
* @tparam T type
* @param item item to insert
* @param index index at which to insert
*/
template<typename T>
void ArrayList<T>::insert(const T& item, const int index) {
if (index < 0 || index >= _size) {
// If the index is out of bounds, throw an exception.
throw std::invalid_argument("Index out of bounds");
}
else {
// Check if there's a need to make additional space.
if (_size >= _capacity) {
resize();
}
ARRAY LIST – DATA
STRUCTURE
Insert operation

// Increase the size of the data structure.


// Note: It's important to do this here, before the for-loop
// below, because the loop needs to shift some elements with one
// position to the right.
++_size;

// Shift all elements from the end down to the provided index
// with one position to the right.
for (int i = _size - 1; i > index; --i) {
_array[i] = _array[i - 1];
}

// Assign the new element.


_array[index] = item;
}
}
ARRAY LIST – DATA
STRUCTURE
Resize operation
Here’s the iconic resize operation.
•This is the operation that can be considered as a drawback to this data structure
because it can be rather slow..
•This is due to the fact that internally the array list can’t simply replace its current
static array just like that. As we mentioned before, it needs to make a new, bigger
static array and populate it with the currently stored data.
•This operation takes time.
ARRAY LIST – DATA
STRUCTURE
Resize operation
/**
* Double the size of the data structure.
*
* @tparam T type
*/
template<typename T>
void ArrayList<T>::resize() {
// Create the new bigger array with double the size of the old one.
T* const newStorage = new T[_capacity *= 2];

// Copy all the elements from the old array to the new one.
for (int i = 0; i < _size; ++i) {
newStorage[i] = _array[i];
}

// Make the bigger array to be the current array of


// the data structure and clear the old one.
T* oldStorage = _array;
_array = newStorage;
delete[] oldStorage;
}
ARRAY LIST – DATA
STRUCTURE
Get operation
And last but not least, the simple get operation, which just retrieves an element by its
index, is a very fast operation.
/**
* Finds and returns an element by its index.
*
* @tparam T type
* @param index index of the required element.
* @return the element required
*/
template<typename T>
T ArrayList<T>::get(const int index) const {
if (index < 0 || index >= _size) {
// If the index is out of bounds, throw an exception.
throw std::invalid_argument("Index out of bounds");
}
else {
// Retrieve the element from the specified index.
return _array[index];
}
}
LINKED LIST
IN
DATA
STRUCTURES
LINKED LIST – DATA
STRUCTURE
•Now it’s time to take a look at the second most common
implementation of a list — the linked list.
•The linked list is a data structure with a similar idea as the array
list but with the difference that its elements are stored in a totally
different way.
•This data structure’s elements are spread across the whole
memory, in contrast to the array list which has them located
sequentially.
Array list
vs.
linked list
memory layout
LINKED LIST – DATA
STRUCTURE
•Since elements are not sequential in memory, each element of the linked list
has to store the address of the next element after it.

The head tag annotates the first element of the list, while the tail annotates the last.

•This brings in a couple of advantages and a couple of disadvantages as


well.
•One of the DISADVANTAGES is that the linked list, due to its
nonsequential memory layout nature, cannot provide fast random access of
elements.
LINKED LIST – DATA
STRUCTURE
•Another thing that’s considered as a drawback is that there’s an
existing memory overhead from all the pointers that are
accompanied with each data node of the data structure.
•Additionally, again due to its nonsequential memory layout, the
linked list cannot gain benefit from the CPU’s cache
optimizations.
•Now let’s continue with the ADVANTAGES.
•One of the advantages is that the linked list can grow safely
without the risk of failing due to memory fragmentation since its
elements are spread across the whole memory. It’s easier to find
free space for a single element than it is for a sequence of
LINKED LIST – DATA
•STRUCTURE
Also, a nice thing to point out is that the linked list uses only as
much memory as it needs, unlike the array list which can have
allocated many additional free slots, thus wasting precious
memory.
Usage
•Same as with the array list data structure, you’ll find the linked list useful
when you need to store some sort of elements but don’t know their total
amount in advance.
•Linked lists are well suited for applications that have the need to often add
or remove elements from the beginning or from the end of a set of
elements.
•It’s not that good of a choice for a data structure if there’s a high demand
LINKED LIST – DATA
STRUCTURE
Complexity
add(element) → O(1)
remove(element) → O(n)
insert(element, index) → O(n)
get(index) → O(n)
LINKED LIST – DATA
STRUCTURE
IMPLEMENTATION OF LINKED LIST
•The linked list has a slightly more complex implementation that
the array list, so be prepared to have many questions.
•Let’s get our hands on with some C++ code.
•Elements in the linked list are called nodes, and they are
connected to each other through a reference. Each node has a
nextElement property.
LINKED LIST – DATA
•STRUCTURE
Let’s first define our elements, which we’ll also call
nodes.
#include<iostream>
using namespace std;
template<typename T>
struct Node {
explicit Node(T elem) {
this->elem = elem;
this->next = nullptr;
}

T elem;
Node* next;
LINKED LIST – DATA
STRUCTURE
template<typename T>
class LinkedList
{ Now let’s define the linked list
private: itself.
Node<T>* _head;
Node<T>* _tail;
int _size;
public:
LinkedList();
void add(const T& item);
T get(int index) const;
void insert(const T& item, int index);
void remove(const T& item);
void removeAt(int index);
bool contains(const T& item) const;
int size() const;
void print() const;
};
LINKED LIST – DATA
STRUCTURE
And its constructor.
template<typename T>
LinkedList<T>::LinkedList() :
_head(nullptr),
_tail(nullptr),
_size(0) {
// Initialize all class variables.
}
•Now let’s show implementations for the most important operations.
LINKED LIST – DATA
STRUCTURE
ADD OPERATION
The add operation is quite efficient because it’s a very easy thing to do.
All that’s needed is to update the tail element.
LINKED LIST – DATA
STRUCTURE
ADD OPERATION
Let’s add few nodes in Linked List i.e, 23, 45, 76, 34, 32

DATA DATA DATA


NEXT NEXT NEXT

23 X
11009 45 X
11207 76 X
10002

10003 11009 11207 DATA


NEXT
ADD NEW
Head pointer Tail pointer ADD
ADD
NODE
ADD NEW
NEW
NEWWITH
DATA 34 X
11297
ADD
NODE
NODENEW
WITHNODE
NEXT
WITH
10003 11207
10003
10002
11297
11009 NODEVALUE
WITH 45
WITH VALUE
VALUE
VALUE
VALUE 4545
76
32
34
3245 X 10002

11297
LINKED LIST – DATA
STRUCTURE
template<typename T>
void LinkedList<T>::add(const T& item) {
// Create a box which wraps the new element.
auto* const newNode = new Node<T>(item);
if (!_head) {
// If the head is NULL then the data structure is empty,
// so make the first element (the head) be the new element.
// Do the same for the tail element.
_head = newNode;
_tail = newNode;
// Increase the size of the data structure.
++_size;
}
else {
// If the head is not null, then we have some elements
// to work with and also the tail is not null.
// Attach the new node to the last element (the tail) and
// make it the new tail.
_tail->next = newNode;
_tail = newNode;
// Increase the size of the data structure.
++_size;
}
}
LINKED LIST – DATA
STRUCTURE
INSERT OPERATION
The overall speed of the insertion operation greatly depends on the position in which
the new element has to be inserted.
All that has to be done here is to just disengage one direct pointer from a specified
element to its next element and to introduce the new element between them.
LINKED LIST – DATA
STRUCTURE
ADD OPERATION
Let’s add a node in Linked List i.e, 23 after 45 LET’S …
HERE
DATA
NEXT
DATA
NEXT
DATA
DATA
NEXT ADD
NEXT DATA
NEXT

23 X
11009 45 X
11207
11607 7623 X11207
X 76 X
10003 11009 11607
11207 11207
ADDNEW
DATA NEXT
AND WE ARE
WHERE ??
Head pointer Tail pointer ADD
ADD
ADD NEW
NODE
ADD NEW
NEW
NEWWITH
ADD
NODE
NODENEW
23WITH NODE
X
11207
WITH
11207
10003
11009 NODE
NODE WITH
VALUE
WITH 45
10003 WITH
VALUE VALUE
45
VALUE23
VALUE
VALUE 76
34
45
11607
45 DONE !!
LINKED LIST – DATA
STRUCTURE
template<typename T>
void LinkedList<T>::insert(const T& item, const int index) {
if (index < 0 || index >= _size) {
// If the index is out of bounds, throw an exception.
throw std::invalid_argument("Index out of bounds");
}
else {
// Create a box which wraps the new element.
auto* const newNode = new Node<T>(item);

// Initialize an index counter and two pointers to


// track two consecutive elements.
int indexCounter = 0;
Node<T>* prev = nullptr;
Node<T>* iter = _head;

// While we haven't reached the desired index,


// move both element pointers to the right.
while (indexCounter++ < index) {
prev = iter;
iter = iter->next;
}
LINKED LIST – DATA
STRUCTURE
if (prev && iter) {
// If we have both pointers pointing to an element,
// then we're somewhere after the first element in the
// data structure, so make the current element's previous
// point to the new element and make the new element point
// to the current one.
prev->next = newNode;
newNode->next = iter;

// Increase the data structure's size.


++_size;
}
else if (iter) {
// If only the pointer to the current element is
// not null, then we're at the first element of the
// data structure (the head), so make the new element
// be the head and point it to the old head element.
_head = newNode;
_head->next = iter;

// Increase the data structure's size.


++_size;
}}}
LINKED LIST – DATA
STRUCTURE
REMOVE AT OPERATION
The removeAt operation has the same complexity as the insert operation.
It’s again just a matter of replacing pointers, but whether the operation is fast or not
depends entirely on the position of the element that has to be removed.
LINKED LIST – DATA
STRUCTURE
Let’s say we have the following Linked List, we want to remove the node with value
93
DATA DATA
DATA DATA
NEXT NEXT
NEXT NEXT

23 X
12003
10603 23
93 XX
10603 23 X
10003 10603
12003 10603
LINKED LIST – DATA
STRUCTURE
What if the element user want to remove is first node ???

DATA DATA DATA


NEXT NEXT NEXT

23 X
12003
10603 93 X
10603 23 X
10003 12003 10603

Head pointer Tail pointer

10003
12003 10603
LINKED LIST – DATA
STRUCTURE
template<typename T>
void LinkedList<T>::removeAt(const int index) {
if (index < 0 || index >= _size) {
// If the index is out of bounds, throw an exception.
throw std::invalid_argument("Index out of bounds");
}
else {
// Create an index counter and two pointers which
// we'll use to keep track of every two consecutive
// elements in the data structure.
int indexCounter = 0;
Node<T>* prev = nullptr;
Node<T>* iter = _head;

// Iterate through elements util


// the desired element index is reached.
while (indexCounter++ < index) {
prev = iter;
iter = iter->next;
}
LINKED LIST – DATA
STRUCTURE
if (prev && iter) {
// If both consecutive elements exist this means
// that we're surely somewhere after the first
// element.

// Check whether we're trying to delete the last element.


// If so, make the previous the new tail.
if (iter == _tail) {
_tail = prev;
}

// Break the connection between the two consecutive


// elements and make the first one point to the
// second one's next element, thus preparing the
// second element for deletion.
prev->next = iter->next;

// Reduce the size of the data structure by 1 and


// delete the second of the two consecutive elements.
--_size;
delete iter;
}
LINKED LIST – DATA
STRUCTURE
else if (iter) {
// If only the second of the two consecutive elements
// exist, this means that we're at the beginning of
// the data structure. In this case we need to move
// the head with one position.
_head = _head->next;

// Reduce the size of the data structure by 1 and


// delete the second of the two consecutive elements.
--_size;
delete iter;
}
}
}
LINKED LIST – DATA
STRUCTURE
REMOVE OPERATION
This operation is again pretty much summed up by the removeAt operation.
template<typename T>
void LinkedList<T>::remove(const T& item) {
// Initialize pointers to two consecutive elements
// with the second one being the first element.
Node<T>* prev = nullptr;
Node<T>* curr = _head;

// Iterate through all two consecutive elements while the second of the two
// consecutive elements is not null.
while (curr) {

// If the current element's value is equal to the


// value we're looking for.
if (curr->elem == item) {

// If the current element is the first element in the data structure,


// then move the head with one position to the right.
if (curr == _head) {
LINKED LIST – DATA
STRUCTURE
// Check whether the head is equal to the tail.
// If so, move the tail, essentially making it null.
if (_head == _tail) {
_tail = _tail->next;
}

// Move the head element.


_head = _head->next;
}

// If the current element's previous element is not null,


// move it to the right.
if (prev) {
prev->next = curr->next;
}

if (curr == _tail) {
// If we happen to remove the last element,
// we should make the previous element the tail.
_tail = prev;
delete curr;
}
LINKED LIST – DATA
STRUCTURE
else {
// Connect the current element's previous with the next element
// in the data structure and delete the current one.
Node<T>* temp = curr;
curr = curr->next;
delete temp;
}

// Reduce the size of the data structure.


--_size;
}
else {
// If the current element's value is not equal
// to the value we're looking for, then just move both
// elements with one position to the right.
prev = curr;
curr = curr->next;
}
}
}
LINKED LIST – DATA
STRUCTURE
GET OPERATION
The get operation is as easy as it sounds. The only problem is that it’s not as fast as
its counterpart in the arrayed list.
template<typename T>
T LinkedList<T>::get(const int index) const {
if (index < 0 || index >= _size) {
// If the index is out of bounds, throw an exception.
throw std::invalid_argument("Index out of bounds");
}
else {
// Initialize an index counter and a pointer to the head element.
int indexCounter = 0;
Node<T>* iter = _head;

// Move the pointer to point to the next element until we


// reach the desired index.
while (indexCounter++ < index) {
iter = iter->next;
}

// Then return that exact item.


return iter->elem;
}}
LINKED LIST – DATA
STRUCTURE
Comparison Between Array List and Linked
•The array list adds and gets elements fast, while the linked list can quickly add or
remove elements at the beginning and the end of the data structure.
•The linked list has no issues with fragmented RAM memory, unlike its alternative,
the array list.
•The array list can benefit from CPU-cache optimizations, unlike its alternative, the
linked list.
•The linked list uses just as much memory as it needs, compared to the array list
which allocates additional memory.
•Which one to use depends entirely on the type of task you want to accomplish.
LINKED LIST – DATA
STRUCTURE
Conclusion

•Both array lists and linked lists are among the most commonly used data structures.
•They fit nearly every use case.
•With that being said, it’s very crucial to feel comfortable using them.

You might also like