B4.1.2 Evaluate linked lists. (HL only)

B4.1.2 Evaluate linked lists. 
• Lists must include singly, doubly, circular 
• Sketch of linked lists and implementation of basic operations diagrammatically, such as insertion, deletion, traversal, search 
• The advantages and disadvantages of using linked lists over other data structures like arrays, particularly in terms of memory utilization and performance

The big idea

A linked list is a data structure where elements (called nodes) are connected by references (or “links”), rather than being stored in a continuous block of memory like an array.

Each node contains:

  • Data – the value stored in the node.
  • Pointer(s) – reference(s) to the next node, and sometimes to the previous node.

Linked lists allow efficient insertion and deletion without shifting elements, but accessing elements requires traversal from the start.

 

1. Types of linked lists

Singly Linked List

  • Each node has data and a pointer to the next node.
  • Last node’s next pointer is None.

Structure:

[Data|Next] → [Data|Next] → [Data|Next] → None

 

Doubly Linked List

  • Each node has data, a pointer to the next node, and a pointer to the previous node.
  • Allows traversal in both directions.

Structure:

None ← [Prev|Data|Next] ↔ [Prev|Data|Next] ↔ [Prev|Data|Next] → None

 

Circular Linked List

  • Similar to singly or doubly, but the last node’s pointer links back to the first node.
  • No None termination; traversal continues in a loop.

Structure (singly circular):

[Data|Next] → [Data|Next] → [Data|Next] ↺

 

2. Basic operations

a) Insertion (singly linked list example)

class Node:
    def __init__(self, data):
        self.data = data
        self.next = None
class SinglyLinkedList:
    def __init__(self):
        self.head = None
    def insert_at_end(self, data):
        new_node = Node(data)
        if not self.head:
            self.head = new_node
            return
        current = self.head
        while current.next:
            current = current.next
        current.next = new_node

 

b) Deletion

    def delete(self, key):
        current = self.head
        # If head node holds the key
        if current and current.data == key:
            self.head = current.next
            return
        prev = None
        while current and current.data != key:
            prev = current
            current = current.next
        if current:  # Key found
            prev.next = current.next

 

c) Traversal

    def traverse(self):
        current = self.head
        while current:
            print(current.data, end=" -> ")
            current = current.next
        print("None")

 

d) Search

    def search(self, key):
        current = self.head
        while current:
            if current.data == key:
                return True
            current = current.next
        return False

 

3. Advantages and disadvantages

AspectLinked ListArray
Memory allocationDynamic — can grow/shrink as needed.Fixed size (unless resized).
Insertion/deletionO(1) if position is known (just change pointers).O(n) because shifting elements is required.
Access timeO(n) — must traverse from start.O(1) — direct index access.
Memory usageHigher (stores extra pointer(s) for each node).Lower (only stores data).
Cache friendlinessPoor — nodes may be scattered in memory.Good — contiguous memory improves CPU cache performance.
Reordering elementsEasy — just change pointers.Costly — requires moving data.

 

4. When to choose linked lists over arrays

  • When frequent insertions/deletions occur, especially in the middle of the collection.
  • When the size of the data changes frequently and unpredictably.
  • When you don’t need fast random access by index.

 

5. Performance summary

OperationSingly/Doubly LL (avg case)Array (avg case)
Access by indexO(n)O(1)
SearchO(n)O(n)
Insert at startO(1)O(n)
Insert at endO(1) with tail pointer, else O(n)O(1) amortized (dynamic array)
Delete at startO(1)O(n)
Delete at endO(n)O(1) amortized