24 Jan 2023, 11:32 PM
24 Jan 2023, 11:32 PM

Linked Lists

A linked list is a data structure that represents a sequence of nodes.
In a singly linked list, each node points to the next node in the linked list.
A doubly linked list gives each node pointers to both the next node and the previous node.
Unlike an array, a linked list does not provide constant time access to a particular "index" within the list. This means that if you'd like to find the Kth element in the list, you will need to iterate through K elements.
The benefit of a linked list is that you can add and remove items from the beginning of the list in constant time.

Linked List implementation

In the below implementation, we have a Linked List class that wraps the Node class.
In fact, if we accessed the linked list through a reference to the head Node of the linked list, we should be a bit careful. What if multiple objects need a reference to the linked list, and then the head of the linked list changes? Some objects might still be pointing to the old head.

My Linked List class implementation in C++

Here you can find the repository on GitHub with the complete code.

Node.cpp
#pragma once

template<typename T>
class Node {
    public:
        T data;
        Node* next;
        
        // default constructor
        Node() = default;

        // constructor with data
        Node(const T& value) : data(value), next{NULL} {}
};
LinkedList.h
#pragma once

#include "Node.cpp"

/* Forward declaration of the template, so that operator<< is regognized as a template */
template<typename T>
class LinkedList;

template<typename T>
std::ostream& operator<< ostream& out, const LinkedList<T>& list;

template<typename T>
class LinkedList {
    protected:
        Node<T>* head;
    public:
        LinkedList() : head(NULL) {}
        LinkedList(const LinkedList& copyList);
        ~LinkedList();
        void insertLast(T value);
        void initRandom(int size);
        void insertFirst(T value);
        void insertOrder(T value);
        int length() const;
        void deleteElement(T value);
        void deleteFirst();
        void deleteLast();
        Node<T>* get(int position) const;
        void reverse();
        void print() const;
        void clear();
        LinkedList<T>& operator= (const LinkedList<T>& list);
        friend std::ostream& operator<< <> ostream& out, const LinkedList<T>& list;
};
LinkedList.cpp
#include <iostream>

#include "LinkedList.h"

template <typename T>
LinkedList<T>::LinkedList(const LinkedList& copyList) {
    Node<T>* newHead = this->head = new Node<T>();
    
    /* I could simply do this->insertlast() but it would be less efficient */
    for(auto tmp = copyList.head; tmp != NULL; tmp = tmp->next) {
        newHead->next = new Node<T>(tmp->data);
        newHead = newHead->next;
    }

    Node<T>* tmp = this->head;
    this->head = this->head->next;
    delete tmp;
}

template <typename T>
LinkedList<T>::~LinkedList() {
    this->clear();
}

template <typename T>
void LinkedList<T>::insertLast(T value) {
    Node<T>* newNode = new Node<T>(value);
    if(this->head == NULL)
        this->head = newNode;
    else {
        Node<T>* current = head;
        for(; current->next != NULL; current = current->next);
        current->next = newNode; 
    }
}

template <typename T>
void LinkedList<T>::initRandom(int size) {
    for(int i = 0; i < size; i++) {
        int random = rand() % 10;
        insertLast(random);
    }
}

template <typename T>
void LinkedList<T>::insertFirst(T value) {
    Node<T>* newHead = new Node<T>(value);
    newHead->next = this->head;
    this->head = newHead;
}

template <typename T>
void LinkedList<T>::insertOrder(T value) {
    if((this->head == NULL) || (value < this->head->data)){
        insertFirst(value);
    }
    else {
        Node<T>* temp = head;
        Node<T>* node = new Node<T>(value);
        for(;(temp->next != NULL) && (value > temp->next->data); temp = temp->next);
        node->next = temp->next;
        temp->next = node;
    }
}

template <typename T>
int LinkedList<T>::length() const {
    int i = 0;
    for (auto current = this->head; current != NULL; current = current->next, i++);
    return i;
}

template <typename T>
void LinkedList<T>::deleteFirst() {
    if(this->head == NULL)
        return;
    Node<T>* temp = this->head;
    this->head = this->head->next;
    delete temp;
}

template <typename T>
void LinkedList<T>::deleteLast() {
    if(this->head == NULL)
        return;
    if(this->head->next == NULL) {
        delete head;
        return;
    }
    
    Node<T>* current = this->head;
    for (; (current->next->next != NULL); current = current->next);
    Node<T>* temp = current->next;
    current->next = current->next->next;
    delete temp;
}

template <typename T>
void LinkedList<T>::deleteElement(T value) {
    if(this->head == NULL)
        return;
    
    Node<T>* current = this->head;

    if (current->data == value)
        deleteFirst();
    
    for (; (current->next != NULL) && (value != current->next->data); current = current->next);
    
    if(current->next == NULL)
        return; 
    
    Node<T>* temp = current->next;
    current->next = current->next->next;
    delete temp;
}

template <typename T>
Node<T>* LinkedList<T>::get(int position) const{
    if(position < 0)
        throw std::out_of_range("Out Of Range!");

    Node<T>* current = this->head;
    int i = 0;
    while(i < position && current != NULL) {
        current = current->next;
        ++i;
    }
    if(current == NULL) throw std::out_of_range("Out Of Range!");
    return current;
}

template <typename T>
void LinkedList<T>::reverse() {
    if(this->head == NULL)
        return;

    Node<T> * previous = NULL, * current = this->head, * next = NULL;

    while(current != NULL){
        next = current->next;
        current->next = previous;
        previous = current;
        current = next;
    }
    this->head = previous;    
}

template <typename T>
void LinkedList<T>::clear() {
    while (this->head != NULL)
        deleteFirst();
}

template <typename T>
void LinkedList<T>::print() const {
    std::cout << *this; 
}
 
template<typename T>
LinkedList<T>& LinkedList<T>::operator= (const LinkedList<T>& copyList) {
    LinkedList<T> temp(copyList);
    std::swap(temp.head, this->head);
    return *this;
    /* Since I swapped the heads, now temp destructor will be called, but temp now is the 
        original list */
}

template <typename T>
std::ostream& operator<< ostream& out, const LinkedList<T>& list {
    for(auto current = list.head; current != NULL; current = current->next)
        out << current->data << " -> ";
    out << "NULL" << std::endl;
    return out;
}