Loading...

DESIGN & ANALYSIS OF ALGORITHMS  

>

LEARNING OUTCOME 6

Linked List

A linked list is a linear data structure where elements are not stored in contiguous memory locations. Instead, each element, called a node, contains data and a pointer to the next node in the sequence. This allows for dynamic memory allocation and efficient insertion and deletion of elements, especially when compared to arrays.

Array-based List vs. Linked List

Feature Array-Based List Linked List
Memory Allocation Contiguous memory allocation Dynamic memory allocation
Access to Elements Direct access using index Sequential access through pointers
Insertion and Deletion Inefficient, requires shifting elements Efficient, especially at the beginning or middle
Space Overhead Fixed memory allocation Additional memory for pointers
Flexibility in Size Fixed size Dynamic size
Memory Fragmentation Can suffer from fragmentation Less prone to fragmentation
Cache Efficiency Generally more cache-friendly Less cache-friendly due to non-contiguous memory

Types of Linked Lists:

Creating a Linked List

Defining the Node Structure

C++

#include <iostream>

struct Node {
    int data; // Data stored in the node (can be any data type)
    Node* next; // Pointer to the next node

    // Constructor to initialize a new node
    Node(int val) : data(val), next(nullptr) {} // Initialize next to null
};

The LinkedList Class

To manage the linked list, it's good practice to create a LinkedList class. This class will encapsulate the operations you can perform on the list.

C++

class LinkedList {
private:
    Node* head; // Pointer to the first node in the list

public:
    LinkedList() : head(nullptr) {} // Constructor: initially empty list

    // ... (Methods for operations like insertion, deletion, etc. will go here)
};

Inserting Elements

Let's implement a method to insert a new node at the beginning of the list (often called "prepend").

C++

void insertAtBeginning(int data) {
    Node* newNode = new Node(data); // Create a new node

    newNode->next = head; // Make the new node point to the current head
    head = newNode;      // Update head to point to the new node
}

Displaying the List

A method to print the contents of the linked list is essential for verification.

C++

void display() {
    Node* current = head; // Start from the head

    while (current != nullptr) { // Traverse the list until the end
        std::cout << current->data << " -> ";
        current = current->next; // Move to the next node
    }
    std::cout << "nullptr" << std::endl; // Mark the end of the list
}

Complete Example

Here's the complete code, combining all the parts:

C++

#include <iostream>

struct Node {
    int data;
    Node* next;
    Node(int val) : data(val), next(nullptr) {}
};

class LinkedList {
private:
    Node* head;

public:
    LinkedList() : head(nullptr) {}

    void insertAtBeginning(int data) {
        Node* newNode = new Node(data);
        newNode->next = head;
        head = newNode;
    }

    void display() {
        Node* current = head;
        while (current != nullptr) {
            std::cout << current->data << " -> ";
            current = current->next;
        }
        std::cout << "nullptr" << std::endl;
    }
};

int main() {
    LinkedList myList;

    myList.insertAtBeginning(10);
    myList.insertAtBeginning(20);
    myList.insertAtBeginning(30);

    myList.display(); // Output: 30 -> 20 -> 10 -> nullptr

    return 0;
}

Other Operations (To Explore)

Memory Management

It's crucial to manage memory properly in C++. When you're finished with a linked list, you need to deallocate the memory used by the nodes to prevent memory leaks. This involves traversing the list and deleting each node. This is commonly done with a destructor in the LinkedList class. Example:

C++

~LinkedList() {
    Node* current = head;
    while (current != nullptr) {
        Node* next = current->next;
        delete current;
        current = next;
    }
    head = nullptr; // Important: set head to null after deleting
}

Linked List Operations

1. Inserting at the Beginning

C++
#include <iostream>

struct Node {
    int data;
    Node* next;
    Node(int val) : data(val), next(nullptr) {}
};

class LinkedList {
private:
    Node* head;

public:
    LinkedList() : head(nullptr) {}

    void insertAtBeginning(int data) {
        Node* newNode = new Node(data);
        newNode->next = head;
        head = newNode;
    }

    // ... (Other methods will go here)
};

int main() {
    LinkedList myList;
    myList.insertAtBeginning(10);
    myList.insertAtBeginning(20);
    myList.insertAtBeginning(30);
    // ...
}

2. Inserting After a Given Node

C++
void insertAfter(Node* prevNode, int data) {
    if (prevNode == nullptr) {
        std::cerr << "Previous node cannot be null" << std::endl;
        return; // Or throw an exception
    }

    Node* newNode = new Node(data);
    newNode->next = prevNode->next;
    prevNode->next = newNode;
}

3. Inserting at the End

C++
void insertAtEnd(int data) {
    Node* newNode = new Node(data);

    if (head == nullptr) { // Empty list
        head = newNode;
        return;
    }

    Node* last = head;
    while (last->next != nullptr) {
        last = last->next;
    }
    last->next = newNode;
}

4. Traversing a Linked List (and Displaying)

C++
void display() {
    Node* current = head;
    while (current != nullptr) {
        std::cout << current->data << " -> ";
        current = current->next;
    }
    std::cout << "nullptr" << std::endl;
}

5. Searching for a Node

C++
Node* search(int key) {
    Node* current = head;
    while (current != nullptr) {
        if (current->data == key) {
            return current; // Found the node
        }
        current = current->next;
    }
    return nullptr; // Not found
}

6. Reversing a Linked List

C++
void reverse() {
    Node* prev = nullptr;
    Node* current = head;
    Node* nextNode = nullptr;

    while (current != nullptr) {
        nextNode = current->next; // Store next node
        current->next = prev;     // Reverse the link
        prev = current;             // Move prev one step forward
        current = nextNode;         // Move current one step forward
    }
    head = prev; // Update head to the new first node
}

7. Deleting a Node

C++
void deleteNode(int key) {
    Node* current = head;
    Node* prev = nullptr;

    // If head node itself holds the key
    if (current != nullptr && current->data == key) {
        head = current->next;  // Changed head
        delete current;       // free memory
        return;
    }

    // Search for the key to be deleted, keep track of the
    // previous node as we need to change 'prev->next'
    while (current != nullptr && current->data != key) {
        prev = current;
        current = current->next;
    }

    // If key was not present in linked list
    if (current == nullptr) return;

    // Unlink the node from linked list
    prev->next = current->next;

    delete current;  // free memory
}

8. Counting Nodes

C++
int countNodes() {
    int count = 0;
    Node* current = head;
    while (current != nullptr) {
        count++;
        current = current->next;
    }
    return count;
}

Complete Example with all modules:

C++
#include <iostream>

// ... (Node structure and LinkedList class as before)

// ... (All the function definitions from above)

int main() {
    LinkedList myList;
    myList.insertAtBeginning(10);
    myList.insertAtEnd(20);
    myList.insertAfter(myList.search(10), 15); // Insert 15 after 10
    myList.display(); // 10 -> 15 -> 20 -> nullptr

    myList.reverse();
    myList.display(); // 20 -> 15 -> 10 -> nullptr

    myList.deleteNode(15);
    myList.display(); // 20 -> 10 -> nullptr

    std::cout << "Number of nodes: " << myList.countNodes() << std::endl; // 2

    return 0;
}

End of Outcome Quiz

1 of 20

    Quiz Score

    Percentage: 0%

    Answered Questions: 0

    Correct Answers: 0

    Faults: