Linked List

 

A linked list data structure that is a collection of elements, called nodes, which consists data and a reference (or link) to the next node in the sequence. This structure allows for efficient insertion and deletion of elements.

Questions List

Mth-to-Last Element of a Linked List

  • Solution 1: Traverse and use other data structure to store index
  • Solution 2: Use Doubly linked list
  • Solution 3: Beginning elements from first to m th node from behind is l. Therefore, l + m = n(total). Traverse l elements from the beginning of the list (SENSE)
  • Solution 4: Use m size queue (Recommended); keeping a queue m elements long
  • Solution 5: Use two pointers; current position pointer and m-behind pointer

List Flattening

  • Solution 1: Use BFS kinda method; start with all the first-level nodes followed by all the second, third, … -level nodes
  • Solution 2: Traverse the first level from the start, following the next pointers. Everytime you encounter node with a child, append the child to the end of the first level and update the tail pointer

    Start at the begining of the first level
    While you are no at the end of the first level
         If the current node has a child
             Append the child to the end of the first level
             Update the tail pointer
         Advance to next node

List Unflattening

  • solution 1: Create separate data structure storing child pointers to all child nodes
  • Solution 2: Find a node with a child, separate the child from its previous node, start traversing the new child list, and then continue traversing the original child list.

    Explore path:
         While not at the end
             If current node ha s a child
                 Separate the child from its previous node
                 Explore path beginning with the child
             Go onto the next node

Null or Cycle

  • Solution 1: Mark each element that you visit. (However, not allowed to modify list)
  • Solution 2: Keep track of the nodes you’ve been encountered by putting them in a separate list
  • Solution 3: Use two pointers

    Start slow pointer at the head of the list
    Start fast pointer at the second node
    Loop infinitely
    If the fast pointer reaches a null pointer
         Return that the list is null terminated
    If the fast pointer moves onto or over the slow pointer
         Return that there is a cycle
    Advance the slow pointer one node Advance the fast pointer two node

Types of Linked Lists

  • Singly Linked List: Each node contains data and a pointer to the next node. The last node points to null, indicating the end of the list.

  • Doubly Linked List: Each node contains data, a pointer to the next node, and a pointer to the previous node. This allows traversal in both directions.

  • Circular Linked List: Similar to singly or doubly linked lists, but the last node points back to the first node, forming a circle.

Advantages

  • Dynamic Size: Unlike arrays, linked lists are dynamic and can grow or shrink in size.

  • Efficient Insertions/Deletions: Adding or removing elements from a linked list is generally more efficient than doing so in an array, especially for large datasets.

  • No Memory Wastage: Linked lists allocate memory as needed, so there’s no unused memory like in arrays.

Disadvantages

  • Memory Usage: Each node in a linked list requires extra memory for the pointer(s), which can be significant compared to arrays.

  • Sequential Access: Elements in a linked list must be accessed sequentially, starting from the first node, making random access slow.

  • Complexity: Implementing and managing a linked list is more complex than an array.

Basic Operations

  • Traversal: Going through each node, usually starting from the head, until you reach the end (or a specific condition).
  • Insertion: Adding a node to the list, either at the beginning, end, or at a specific position.
  • Deletion: Removing a node from the list, which involves adjusting the pointers of adjacent nodes.
  • Search: Finding a node in the list with a specific value.

LinkedList.h

#ifndef PIE_LINKEDLIST_H
#define PIE_LINKEDLIST_H

#include <iostream>
#include <cstring>

using namespace std;

template <typename T>
class LinkedList {
public:
    LinkedList() : next(nullptr), data(T()) {}
    LinkedList(const T &value) : next(NULL), data(value) {}
    LinkedList(const LinkedList &cp) : next(NULL), data(cp.data) {
        if (!cp.next)
            next = new LinkedList<T>(*cp.next);
    }

    T& getData() const;
    
    // TODO: Need to understand this
    bool insertAtFront(LinkedList<T> **head, T val);

    // TODO: Need to understand this
    bool insertAtLast(LinkedList<T> **head, T val);

    // TODO: Need to understand this
    bool insertAtIndex(LinkedList<T> **head, T val, int index);
    
    LinkedList<T>* search(LinkedList<T> **head, const T& value);

    bool deleteElement(LinkedList<T> **head, LinkedList<T> *delNode);
    
    void clear(LinkedList<T> **head);
    
    LinkedList& operator=(const LinkedList &rhs) {
        if (this != &rhs) {
            delete next;  // Free existing resources

            data = rhs.data;
            next = (rhs.next ? new LinkedList<T>(*rhs.next) : nullptr);
        }
        return *this;
    }

    class Iterator {
    public:
        Iterator(LinkedList<T>* node) : currentNode(node) {}

        // Dereference operator
        T& operator*() const {
            return currentNode->data;
        }

        // Pre-increment operator
        Iterator& operator++() {
            currentNode = currentNode->next;
            return *this;
        }

        // Equality comparison
        bool operator!=(const Iterator& other) const {
            return currentNode != other.currentNode;
        }
    private:
        LinkedList<T>* currentNode;
    };

    // Methods to get iterators
    Iterator begin() { return Iterator(this); }
    Iterator end() { return Iterator(nullptr); }

private:
    LinkedList  *next;
    T            data;
};

#endif //PIE_LINKEDLIST_H

LinkedList.cpp

#include "LinkedList.h"


template<typename T>
T &LinkedList<T>::getData() const {
    return data;
}


template<typename T>
bool LinkedList<T>::insertAtLast(LinkedList<T> **head, T val) {
    auto *newNode = new LinkedList<T>(val);

    if (!newNode) return false;

    if (*head == NULL) {
        newNode->next = *head;
        *head = newNode;
        return true;
    }
    else {
        LinkedList<T> *temp = *head;
        while (temp->next) {
            temp = temp->next;
        }
        temp->next = newNode;
        return true;
    }
}


template<typename T>
bool LinkedList<T>::insertAtFront(LinkedList<T> **head, T val) {
    auto *newNode = new LinkedList<T>(val);
    if (newNode == nullptr) return false;
    else {
        newNode->data = val;
        newNode->next = *head;
        *head = newNode;
        return true;
    }
}


template<typename T>
bool LinkedList<T>::insertAtIndex(LinkedList<T> **head, T val, int index) {
    LinkedList<T> *current = *head;
    if (*head == nullptr)   return false;
    if (index <= 0)         return false;

    auto *newNode = new LinkedList<T>(val);
    if (!newNode)           return false;

    if (index == 1) {
        *head = newNode;
        newNode->next = current;
        return true;
    }
    else {
        for (int i = 1; i < index; ++i) {
            // If index is greater than the number of nodes in the list
            if (current->next == nullptr) {
                delete newNode;
                return false;
            }
            current = current->next;
        }

        current->next = newNode;
        newNode->next = current->next;
        return true;
    }
}


template<typename T>
LinkedList<T>* LinkedList<T>::search(LinkedList<T> **head, const T& value) {
    LinkedList<T> *current = *head;

    while (current != nullptr) {
        if (current->data == value) {
            return current; // Value found
        }
        current = current->next;
    }

    return nullptr; // Value not found
}


template<typename T>
bool LinkedList<T>::deleteElement(LinkedList<T> **head, LinkedList<T> *delNode) {
    if (*head == nullptr || delNode == nullptr) {
        return false;
    }

    if (*head == delNode) {
        LinkedList<T> *temp = *head;
        *head = (*head)->next;
        delete temp;
        return true;
    }

    LinkedList<T> *current = *head;
    LinkedList<T> *prev = nullptr;
    while (current != nullptr && current != delNode) {
        prev = current;
        current = current->next;
    }

    // Node not found
    if (current == nullptr) {
        return false;
    }

    // Node found
    prev->next = current->next;
    delete current;
    return true;
}


template<typename T>
void LinkedList<T>::clear(LinkedList<T> **head) {
    if (*head == nullptr) return;
    LinkedList<T> *delNode = *head;
    while (delNode) {
        LinkedList<T> *after = delNode->next;
        delete delNode;
        delNode = after;
    }
    *head = nullptr;
}

main.cpp

#include "LinkedList.h"
#include "LinkedList.cpp"
#include <ctime>


typedef struct cards{
    char* shape;
    int num;
} card;

const char* shapes[] = {"Heart", "Diamond", "Clover", "Spade"};

card generateRandomCard() {
    card newCard;
    newCard.shape = (char*)shapes[std::rand() % 4]; // Randomly select a shape
    newCard.num = std::rand() % 13 + 1; // Randomly select a number between 1 and 13
    return newCard;
}

int main() {
    std::srand(std::time(nullptr));
    LinkedList<card>* head = nullptr;

    for (int i = 0; i < 5; i++) {
        card randomCard = generateRandomCard();
        head->insertAtFront(&head, randomCard);
    }

//    for (auto it = head->begin(); it != head->end(); ++it) {
//        card c = *it;
//        std::cout << "Card: " << c.shape << " " << c.num << std::endl;
//    }

    for (auto c : *head) {
        std::cout << "Card: " << c.shape << " " << c.num << std::endl;
    }
    
    head->clear(&head);
}

Mth-to-Last Element of a Linked List - Solution 5

/*
 * Mongan, J., Kindler, N., & Giguère, E. (2012). 
 * Programming Interviews Exposed: Secrets to Landing Your Next Job (3rd ed.). p.52
 */
LinkedList *findMToLastElement(LinkedList *head, int m) {
    LinkedList *current, *mBehind;
    int i = 0;
    
    if (!head) return NULL;
    
    /* Advance current m elements from beginning
     * checking for the end of the list     
     */
    current = head;
    for (int i = 0; i < m; i++) {
        if (current->next) 
            current = current->next;
        else
            return NULL;
    }
    
    /* Start mBehind at beginning and advance pointers
     * together until current hits last element
     */
    mBehind = head;
    while (current->next) {
        current = current->next;
        mBehind = mBehind->next;
    }
    
    /* mBehind now points to the element we were
     * searching for, so return it
     */
    return mBehind;
}

List Flattening - Solution 2

/*
 * Mongan, J., Kindler, N., & Giguère, E. (2012). 
 * Programming Interviews Exposed: Secrets to Landing Your Next Job (3rd ed.). p.55
 */
void *flattenList(Node *head, Node **tail) {
    Node *curNode = head;
    while (curNode) {
        /* The Current node has a child */
        if (curNode->child) {
            append(curNode->child, tail);
        }
        curNode = curNode->next;
    }
}


/* Appends the child list to the end of the tail and updates the tail */
void append(Node *child, Node **tail) {
    Node *curNode;
    
    /* Append the child list to the end */
    (*tail)->next = child;
    child->prev = *tail;
    
    /* Find the new tail, which is the end of the appended child list */
    for (curNode = child; curNode->next; curNode = curNode->next) {
        /* Intentionally empty for loop */
    }
    
    /* Update the tail pointer now that curNode is the new tail */
    *tail = curNode;
}

List Unflattening - Solution 2

/*
 * Mongan, J., Kindler, N., & Giguère, E. (2012). 
 * Programming Interviews Exposed: Secrets to Landing Your Next Job (3rd ed.). p.57-58
 */    

/* UnflattenList wratps the recursive function and updates the tail pointer */
void unflattenList(Node *start, Node **tail) {
    Noce *curNode;
    
    exploreAndSeparate(start);
    
    /* Update the tail pointer */
    for (curNode = start; curNode->next; curNode = curNode->next) {
        /* Body intentionally empty */
    }
    
    *tail = curNode;
}

/* exploreAndSeparate actually does the recursion and separation */
void exploreAndSeparate(Node *childStart) {
    Node *curNode = childListStart;
    
    while (curNode) {
        if (curNode->child->prev) {
            /* terminates the child list before the child */
            curNode->child->prev->next = NULL;
            /* starts the child list beginning with the child */
            curNode->child->prev = NULL;
            exploreAndSeparate(curNode->child);
        }
        curNode = curNode->next;
    }
}

Null or Cycle - Solution 3

/*
 * Mongan, J., Kindler, N., & Giguère, E. (2012). 
 * Programming Interviews Exposed: Secrets to Landing Your Next Job (3rd ed.). p.60
 */    

/* Takes a pointer to the head of a linked list and determines 
 * if the list ends in a cycle or is NULL terminated 
 */
bool isCyclic(Node *head) {
    Node *fast, *slow;
    slow = head;
    fast = head->next;
    while(1) {
        if (!fast || !fast->next) return false;
        else if (fast == slow || fast->next == slow) return true;
        else {
            slow = slow->next;
            fast = fast->next->next;
        }
    }
}