Operations on Linked Lists: CSU1051 - Data Structures & Algorithms

Operations on linked lists - Insertion, Deletion and Traversal

Executive Summary: Navigating Linked Lists

As you embark on this in-depth exploration of operations on linked lists, anticipate traversing intricate paths and discovering new ways to handle nodes. Your journey will start at the base camp of understanding linked lists - their structure and practical applications. As the expedition progresses, you will delve into fundamental operations including insertion, deletion, and traversal. This endeavor goes beyond the norm by discussing the intricacies and peculiarities of these operations and exploring corner cases, performance implications, and theoretical complexity. We present concrete, real-world problems with comprehensive solutions using C++ code to illuminate these abstract concepts. Along the way, we'll reveal concepts that you may find thought-provoking. Finally, we tie all these threads together in an engaging, inspiring conclusion that prompts further exploration.

1. Introduction: Linked Lists - The Cornerstone of Dynamic Data Structures

Imagine you're building a dynamic data structure, needing to accommodate an unpredictable number of elements with frequent additions and deletions. One efficient way to solve this problem is by utilizing a linked list. A linked list is a linear data structure comprising nodes, where each node contains a data element and a reference (or link) to the next node in the sequence. This structure allows for efficient insertions and deletions at any position in the sequence.

In comparison to arrays, linked lists offer the advantage of efficient insertions and deletions without the need for shifting elements, making them ideal for certain scenarios. However, they lack direct access to individual elements and can consume more memory due to the storage required for links.

2. Types of Linked Lists

Linked lists can take different forms based on how nodes are linked together:

Our focus will be primarily on singly linked lists for simplicity, although the principles discussed apply to all types with minor adjustments.

3. Operations on Linked Lists

Three fundamental operations on linked lists we will examine in detail are: Insertion, Deletion, and Traversal.

3.1 Insertion Operation

The insertion operation adds a new node to the list. Depending on the position where the node is inserted, it can be categorized into three types:

3.1.1 Insertion at the Beginning

Inserting a new node at the beginning involves creating a new node, setting its link to point to the current head, and updating the head to the new node. This operation is generally constant time O(1), irrespective of the list's size.

// CPP program to insert a new node at the beginning of a Singly Linked List
#include <bits/stdc++.h>
using namespace std;

class Node {
public:
   int data;
   Node* next;
};

void push(Node** head_ref, int new_data) {
    Node* new_node = new Node();
    new_node->data = new_data;
    new_node->next = (*head_ref);
    (*head_ref) = new_node;
}
3.1.2 Insertion at the End

For inserting at the end, the new node should be linked to the last node in the list. This operation involves traversing the entire list, resulting in a time complexity of O(n), where n is the number of elements in the list.

// CPP program to insert a new node at the end of a Singly Linked List
#include <bits/stdc++.h>
using namespace std;

class Node {
public:
   int data;
   Node* next;
};

void append(Node** head_ref, int new_data) {
    Node* new_node = new Node();
    new_node->data = new_data;
    new_node->next = NULL;
    if (*head_ref == NULL) {
       *head_ref = new_node;
       return;
    }
    Node* last = *head_ref;
    while (last->next) {
       last = last->next;
    }
    last->next = new_node;
}
3.1.3 Insertion at a Given Position

Inserting a node at a given position requires traversing the list to the desired position and adjusting the links accordingly. The new node points to the node previously at this position, and the preceding node now points to the new node. This operation has a time complexity of O(k) where k is the position where the new node will be inserted.

// CPP program to insert a new node at a specific position in a Singly Linked List
#include <bits/stdc++.h>
using namespace std;

class Node {
public:
   int data;
   Node* next;
};

void insertAfter(Node* prev_node, int new_data) {
    if (prev_node == NULL) {
       cout << "The given previous node cannot be NULL";
       return;
    }
    Node* new_node = new Node();
    new_node->data = new_data;
    new_node->next = prev_node->next;
    prev_node->next = new_node;
}

3.2 Deletion Operation

The deletion operation removes a node from the linked list. It requires the position or key of the node to be deleted. This operation includes two tasks: locating the node to be deleted and changing the 'next' link of the preceding node. If the head node is deleted, it should be updated to the second node in the list. The deletion operation has a time complexity of O(n) because it might need to traverse the entire list.

// CPP program to delete

 a node from a Singly Linked List
#include <bits/stdc++.h>
using namespace std;

class Node {
public:
   int data;
   Node* next;
};

void deleteNode(Node **head_ref, int key) {
    Node* temp = *head_ref;
    Node* prev = NULL;

    if (temp != NULL && temp->data == key) {
       *head_ref = temp->next;
       delete temp;
       return;
    }

    while (temp != NULL && temp->data != key) {
       prev = temp;
       temp = temp->next;
    }

    if (temp == NULL)
       return;

    prev->next = temp->next;
    delete temp;
}

3.3 Traversal Operation

The traversal operation visits each node in the linked list. This operation is commonly used to perform actions on each node, such as printing data. The time complexity of this operation is O(n), as it visits each node exactly once.

// CPP program to traverse a Singly Linked List
#include <bits/stdc++.h>
using namespace std;

class Node {
public:
   int data;
   Node* next;
};

void printList(Node *node) {
    while (node != NULL) {
       cout << node->data << " ";
       node = node->next;
    }
}

4. Conquering Linked Lists

The power of linked lists lies in their versatility and flexibility, allowing for dynamic memory allocation, efficient insertions and deletions, and ease of implementation. They form the basis for many complex data structures and applications. Whether you're a beginner grasping the ropes or a research scholar seeking deeper insights, we believe this comprehensive exploration of linked lists and their operations has added/revised the valuable layers to your understanding.

As we wrap up this deep dive, we invite you to look forward to our next expedition. In the upcoming article, we will venture into the intriguing world of 'Trees,' exploring their various types, traversals, and operations. Our journey will delve into fascinating concepts such as binary trees, AVL trees, and B-Trees, unearthing the principles behind database indexing, filesystems, and more. The tree is a fundamental data structure, and its roots reach deep into the core of computer science. So, gear up for another enriching exploration!