Lecture 8 - LinkedList - BSSE - Evening - 3B
Lecture 8 - LinkedList - BSSE - Evening - 3B
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.
The array list from the picture above has reached its capacity, and on the next add
operation, the resizing mechanism will be triggered.
// 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.
// 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];
}
// Copy all the elements from the old array to the new one.
for (int i = 0; i < _size; ++i) {
newStorage[i] = _array[i];
}
The head tag annotates the first element of the list, while the tail annotates the last.
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
23 X
11009 45 X
11207 76 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);
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 ???
23 X
12003
10603 93 X
10603 23 X
10003 12003 10603
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 all two consecutive elements while the second of the two
// consecutive elements is not null.
while (curr) {
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;
}
•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.