A Linked List is a linear data structure, like an array, but its elements are not stored in contiguous memory locations. Instead, elements are linked using pointers.
Each element in a linked list is called a node. A node consists of two parts:
The entry point to the linked list is a special pointer called the head, which points to the first node. The last node in the list has its next pointer set to NULL.
| Feature | Array | Linked List |
|---|---|---|
| Memory | Contiguous memory allocation. | Non-contiguous (dynamic) allocation. |
| Size | Static (fixed size). Must be known at compile time. | Dynamic (size can grow and shrink at runtime). |
| Access | Fast random access (O(1)). Can access arr[i] directly. |
Slow sequential access (O(n)). Must traverse from the head. |
| Insertion/Deletion | Slow (O(n)). Requires shifting elements. | Fast (O(1)), if the position (node) is known. |
| Memory Overhead | None. | Extra memory is required for the next pointer in each node. |
This is the standard linked list described above, where each node has one pointer (next) pointing to the next node in the list. Traversal is one-way (forward) only.
Basic Operations:
head, follow next pointers until NULL.next to the current head. Update head to point to the new node.next to the new node.head in a temp node. Update head = head.next. Free the temp node.next to NULL. Free the last node.head before you update head to point to the new node.
A doubly linked list (DLL) node contains two pointers in addition to the data:
NULL).NULL for the head).Advantages over Singly Linked List:
node.prev.Disadvantage:
prev pointer.next and prev) must be updated.A circular linked list is a list where the last node does not point to NULL. Instead, its next pointer points back to the head node, forming a circle.
Advantages:
A Circular Doubly Linked List also exists, where the head.prev points to the last node, and the last.next points to the head.
This refers to the comparison of implementing linear lists using arrays versus linked lists, as detailed in the table above.
As discussed in Unit 1, the array implementation of a stack has a fixed-size limitation. A linked list is a perfect solution for this.
A stack is implemented using a standard singly linked list. The LIFO behavior is achieved by performing push and pop operations at the beginning (head) of the list.
head pointer of the list acts as the top of the stack.This implementation is very efficient and overcomes the overflow problem (it's only limited by the total system memory).
While possible, using a circular list to implement a stack is less common. One could maintain a single pointer last (pointing to the last node). The "top" of the stack would be last.next (the head). Pushing and popping would still happen at the "head" (last.next), which remains an O(1) operation.
A self-organizing list is a linked list that reorders its nodes based on access patterns to improve average search time. The goal is to keep frequently accessed elements near the front of the list.
Common heuristics:
A skip list is a probabilistic data structure that provides efficient search (average O(log n)) in a sorted list. It's essentially a linked list with "express lanes."
It's built in layers. The bottom layer (Level 0) is a regular sorted linked list. Each higher layer (Level 1, Level 2, ...) contains a subset of the nodes from the layer below. A node in Level i has a random probability of also appearing in Level i+1.
To search, you start at the highest layer. You traverse forward until you find a node greater than your target. You then drop down one layer and continue. This "skipping" allows you to bypass large portions of the list, achieving logarithmic time complexity, similar to a balanced binary search tree.
A Queue is a linear data structure that follows the FIFO (First-In, First-Out) principle. This means the element added first is the first one to be removed. Think of it as a line of people waiting for a ticket.
The primary operations are:
A queue is managed with two pointers: front (points to the first element) and rear (points to the last element).
A simple 1D array can be used, but it's inefficient. If you dequeue, the front pointer moves, and that space is "lost." You would have to shift all elements (O(n)), which is slow.
A much better solution is a Circular Array (or Circular Queue).
queue[MAX_SIZE] and two indices, front and rear.front = -1 and rear = -1.rear using modulo arithmetic: rear = (rear + 1) % MAX_SIZE. Then insert the element.front using modulo arithmetic: front = (front + 1) % MAX_SIZE. Then return the element.(rear + 1) % MAX_SIZE == frontfront == rear (or front == -1)This is a more flexible implementation that avoids the fixed-size limit. A singly linked list is used with two external pointers: front and rear.
front points to the head of the list.rear points to the last node of the list.rear.next to it, and update rear.front, update front = front.next, and free the old front node.rear pointer to NULL (not just front).
A Deque (pronounced "deck"), or Double-Ended Queue, is a generalization of a queue that allows insertion and deletion from both ends (front and rear).
Operations:
It can be implemented using either a circular array or a doubly linked list.
A Priority Queue is an abstract data type where each element has an associated priority. The operations are:
This is different from a normal queue; the "first-in" element is not necessarily the "first-out."
Implementations: