DEV Community

Cover image for Understanding Linked Lists: A Solution to Array Limitations
Md Mohosin Ali Shah
Md Mohosin Ali Shah

Posted on

Understanding Linked Lists: A Solution to Array Limitations

Arrays are one of the fundamental data structures in programming. While versatile and efficient for many use cases, they come with certain limitations:

  1. Fixed Size: Once an array is declared, its size cannot be dynamically altered. This can lead to either inefficient memory usage or insufficient space to hold data.
  2. Sequential Memory Allocation: Arrays store their elements in contiguous memory locations. This requirement can cause memory fragmentation issues when sufficient contiguous memory is unavailable.

To overcome these challenges, we use linked lists. Linked lists are dynamic data structures that store data in non-contiguous memory locations, allowing flexibility in size and efficient memory utilization.


The Problem with Sequential Memory Allocation in Arrays

When you declare an array, all its elements are stored in contiguous memory locations. For example:

int a[5] = {1, 2, 3, 4, 5};
Enter fullscreen mode Exit fullscreen mode

If each integer occupies 4 bytes, the elements of this array might be stored in memory locations 150, 154, 158, 162, and 166. Contiguous storage is efficient for accessing elements, but it poses challenges when memory is fragmented. If there isn’t enough contiguous free space, the array cannot be allocated even if the total free memory is sufficient.

Example Issue with Arrays

Suppose your system has 10 blocks of free memory, but they are scattered as follows:

Memory: [Free, Used, Free, Used, Free, Free, Used, Free, Free, Free]
Enter fullscreen mode Exit fullscreen mode

If an array of size 5 requires contiguous blocks, allocation fails, even though enough blocks exist.

This is where linked lists offer a solution by allowing non-contiguous memory allocation. Each element (node) in a linked list is stored independently, with pointers connecting them to form the list.


Linked Lists: A Dynamic and Flexible Solution

A linked list is a linear data structure where each element, called a node, contains two components:

  1. Data: The value stored in the node.
  2. Next: A pointer to the next node in the sequence.

The nodes are dynamically allocated and linked together using pointers, making linked lists inherently dynamic and capable of growing or shrinking as needed. Unlike arrays, linked lists eliminate the need for contiguous memory allocation, making them resilient to fragmentation.

Key Advantages of Linked Lists

  • Dynamic Size: Linked lists can grow or shrink during runtime without the need to pre-allocate or reallocate memory.
  • Efficient Insertions and Deletions: Adding or removing elements in a linked list is faster than in arrays, especially in the middle of the structure.
  • No Contiguous Memory Requirement: Nodes can be stored anywhere in memory, eliminating fragmentation issues.

Types of Linked Lists

  1. Singly Linked List: Each node points to the next node, allowing traversal in one direction.
  2. Doubly Linked List: Each node has two pointers—one pointing to the next node and another to the previous node—enabling bidirectional traversal.
  3. Circular Linked List: The last node points back to the first node, forming a circular structure.

Creating and Managing Linked Lists in C++

To create a linked list in C++, we define a Node class to represent each node. Each node contains:

  1. Data: The value stored in the node.
  2. Next: A pointer to the next node in the list.

Example: Defining a Node Class

class Node {
public:
    int val;  
    Node* next;  

    Node(int val) {  
        this->val = val;
        this->next = NULL;
    }
};
Enter fullscreen mode Exit fullscreen mode

Implementing a Singly Linked List

Example 1: Manual Node Linking

int main() {
    Node a(10), b(20), c(30);

    a.next = &b;
    b.next = &c;

    Node* temp = &a;
    while (temp != NULL) {
        cout << temp->val << " ";
        temp = temp->next;
    }

    return 0;
}
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • Nodes a, b, and c are manually linked.
  • The while loop traverses the list, printing node values.

Example 2: Dynamic Node Creation

int main() {
    Node* head = new Node(10);
    Node* a = new Node(20);
    Node* b = new Node(30);

    head->next = a;
    a->next = b;

    Node* temp = head;
    while (temp != NULL) {
        cout << temp->val << " ";
        temp = temp->next;
    }

    return 0;
}
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • Nodes are dynamically allocated using new.
  • This approach allows for flexible runtime list creation.

Traversing and Printing a Linked List

Traversal involves iterating through the list starting from the head node and following the next pointers until reaching a node with next = NULL.

Example: Print Function

void printList(Node* head) {
    Node* temp = head;
    while (temp != NULL) {
        cout << temp->val << " ";
        temp = temp->next;
    }
}

int main() {
    Node* head = new Node(10);
    Node* a = new Node(20);
    Node* b = new Node(30);

    head->next = a;
    a->next = b;

    printList(head);

    return 0;
}
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • The printList function abstracts traversal logic for better code reuse.

Memory Management in Linked Lists

Memory for linked list nodes is allocated dynamically. When nodes are no longer needed, they should be deallocated using delete to prevent memory leaks.

Example: Deleting Nodes

delete head;
delete a;
delete b;
Enter fullscreen mode Exit fullscreen mode

Proper memory management ensures efficient resource utilization.


Advantages and Disadvantages of Linked Lists

Advantages:

  1. Dynamic Size: The size of the list can be adjusted during runtime.
  2. Efficient Insertions/Deletions: Operations are efficient, especially in the middle of the list.
  3. No Memory Fragmentation: Nodes do not need to be stored in contiguous memory blocks.

Disadvantages:

  1. Extra Memory Overhead: Each node requires additional memory for the pointer(s).
  2. Slower Access Times: Accessing an element requires traversal, unlike arrays that allow direct access using indices.

Conclusion

Linked lists are a powerful alternative to arrays when dynamic size and efficient insertions or deletions are required. By allowing non-contiguous memory allocation, linked lists overcome the limitations of sequential memory allocation in arrays.

  • Use singly linked lists for simple, one-directional traversal.
  • Use doubly linked lists for more complex operations requiring bidirectional traversal.

Understanding linked lists and their implementation is crucial for building dynamic data structures and solving problems where memory efficiency and flexibility are key.

Top comments (0)