Chapter 5 : Linked Implementations of Collection ADTs
π― Learning Objectives
By the end of this study session, youβll be able to:
- Explain why we need linked structures instead of just using arrays for everything
- Describe what linked structures are and how they solve array limitations
- Understand the basic building blocks: nodes, references, and the three fundamental rules of linked lists
- Implement the List ADT using linked structures with all major operations (add, remove, traverse)
- Implement the Stack ADT using linked structures for push/pop operations
- Implement the Queue ADT using multiple approaches (linear, circular, doubly-linked)
- Compare the efficiency trade-offs between array-based and linked implementations
- Recognize when to use linked structures vs. arrays in real programming scenarios
- Apply concepts like head/tail references and traversal techniques in your own code
π The Big Picture
Hereβs the fundamental question this chapter answers: What happens when arrays just arenβt flexible enough?
Think about it - arrays are great when you know exactly how much data youβll have, but what if your data grows and shrinks unpredictably? What if inserting or removing items in the middle requires shifting hundreds of elements? This is where linked structures come to the rescue.
The core concept is brilliant in its simplicity: instead of storing all your data in one big block (like an array), you create individual containers (called nodes) that can live anywhere in memory, and you connect them with βpointersβ or βreferencesβ that tell you where to find the next piece of data. Itβs like having a treasure hunt where each clue leads you to the next location!
This approach gives us dynamic flexibility - our data structure can grow as large as needed and shrink when items are removed, all without the overhead of shifting elements around.
π Core Concepts
Why Arrays Sometimes Fall Short
Letβs start with understanding the problems weβre trying to solve:
Problem 1: Fixed Size Inflexibility
- If your array is too small, you have to create a bigger one and copy everything over (expensive!)
- If your array is too big, youβre wasting memory for unused space
Problem 2: Shifting Overhead
- Want to insert something in the middle? You have to shift all elements to the right
- Want to remove something? You have to shift all elements to the left
- This can be very time-consuming for large arrays
The Solution Vision Instead of these problems, wouldnβt it be great if we could:
- Allocate space for each item only when we actually need it
- Remove space when we delete items
- Add and remove items by just adjusting connections, not shifting everything
This is exactly what linked structures give us!
The Desk Analogy: Making Linked Structures Concrete
The textbook uses a fantastic analogy that makes this concept crystal clear. Imagine a classroom where students can request desks as needed:
Visual Reference (Slides 6-13):
The figures show a teacher with a reference to desk addresses, creating chains of desks where each desk has the address of the next desk written on it.
How It Works:
- Each desk (like a data container) has a unique address
- Each desk can hold a piece of paper with the address of another desk
- The teacher only needs to remember the address of the first desk
- To find any desk, you start at the first one and follow the chain of addresses
Adding New Desks:
- You can add to the beginning (fastest - just update the teacherβs reference)
- You can add to the end (need to walk to the end first)
- You can add in the middle (need to find the right spot and adjust connections)
This desk analogy perfectly illustrates how linked structures work in computer memory!
Basic Linked List Concepts
Now letβs translate that desk analogy into programming terms:
What is a Node? A node is like one of those desks - itβs a container that holds two things:
- Data part: The actual information you want to store
- Link part: The address (reference) to the next node in the chain
Visual Reference (Slide 20):
βββββββββββ¬ββββββββββ
β Data β Next β β This is one node
β Part β Link β
βββββββββββ΄ββββββββββ
What is a Linked List? A linked list is a collection of nodes where:
- Each node points to the next node in the sequence
- The last node points to
null(indicating the end) - We keep a reference to the first node (called the βheadβ)
Visual Reference (Slide 19):
head β [Data|β’] β [Data|β’] β [Data|β’] β [Data|X]
β
null
The Three Sacred Rules of Linear Linked Lists
These are absolutely crucial - memorize them!
- External Reference Rule: There MUST be an external reference (pointer) to the first node so you can access the linked list
- Last Node Rule: The last nodeβs next field MUST be
null - Connection Rule: Every other nodeβs next field MUST contain the address of the next node
Break any of these rules, and your linked list wonβt work correctly!
Node Class Implementation
Hereβs how we implement a node in Java (this becomes an inner class):
private class Node {
private T data; // The actual data
private Node next; // Reference to next node
// Constructor for node with just data
private Node(T data) {
this.data = data;
next = null;
}
// Constructor for node with data and next reference
private Node(T data, Node next) {
this.data = data;
this.next = next;
}
}Key Insight: We make Node a private inner class because itβs an implementation detail that clients shouldnβt see or manipulate directly.
Traversing a Linked List
Traversal means visiting each node in sequence from first to last. Hereβs the essential pattern:
Node currentNode = firstNode; // Start at the beginning
while (currentNode != null) {
// Do something with currentNode.data
currentNode = currentNode.next; // Move to next node
}Memory Trick: Think of traversal like following a trail of breadcrumbs - you start at the first crumb and follow each one to the next until you reach the end!
Linked Implementation of List ADT
Now letβs see how to implement all the List operations using linked structures:
Class Structure
public class LinkedList<T> implements ListInterface<T> {
private Node firstNode; // Reference to first node
private int numberOfEntries; // Count of items in list
// Node class goes here as inner class
// Methods go here
}Adding Elements: Three Scenarios
Scenario 1: Adding to an Empty List
Visual Reference (Slide 31):
Before: firstNode β null
newNode β [Data|null]
After: firstNode β [Data|null]
This is the simplest case - just make firstNode point to the new node.
Scenario 2: Adding to the Beginning
Visual Reference (Slide 33):
Before: firstNode β [A|β’] β [B|β’] β [C|null]
newNode β [NEW|null]
After: firstNode β [NEW|β’] β [A|β’] β [B|β’] β [C|null]
Steps:
- Make the new node point to the current first node
- Make
firstNodepoint to the new node
Scenario 3: Adding to the End
Visual Reference (Slide 32):
Before: firstNode β [A|β’] β [B|β’] β [C|null]
newNode β [NEW|null]
After: firstNode β [A|β’] β [B|β’] β [C|β’] β [NEW|null]
Steps:
- Traverse to find the last node (the one pointing to null)
- Make the last node point to the new node
Adding in the Middle
Visual Reference (Slide 34):
Before: firstNode β [A|β’] β [B|β’] β [C|null]
newNode β [NEW|null]
After: firstNode β [A|β’] β [NEW|β’] β [B|β’] β [C|null]
Steps:
- Traverse to find the node BEFORE where you want to insert
- Make the new node point to the node that comes after
- Make the previous node point to the new node
Removing Elements: Three Scenarios
Removing the First Node
Visual Reference (Slide 42):
Before: firstNode β [A|β’] β [B|β’] β [C|null]
After: firstNode β [B|β’] β [C|null]
(A node becomes unreachable and gets garbage collected)
Removing a Middle Node
Visual Reference (Slide 43):
Before: β’ β [A|β’] β [B|β’] β [C|β’] β β’
After: β’ β [A|β’] β [C|β’] β β’
(B node becomes unreachable)
Key insight: You need to find the node BEFORE the one you want to remove, then βbridge overβ it.
Removing the Last Node Similar to middle removal, but youβre making the second-to-last node point to null instead of another node.
Performance Analysis: Linked vs. Array Lists
Visual Reference (Slide 47):
Hereβs the efficiency comparison table:
| Operation | Array-Based | Linked |
|---|---|---|
| add(newEntry) | O(1) | O(n) |
| add(position, entry) | O(n) | O(n) |
| remove(position) | O(n) | O(n) |
| getEntry(position) | O(1) | O(n) |
Key Insights:
- Arrays are faster for direct access (getting an element at a specific position)
- Linked lists are better when you frequently add/remove at the beginning
- Both have similar performance for adding/removing in the middle
Improving Performance with Tail References
One optimization is keeping a reference to the last node:
private Node firstNode;
private Node lastNode; // New: reference to last node
private int numberOfEntries;Visual Reference (Slide 49):
firstNode β [A|β’] β [B|β’] β [C|null] β lastNode
Benefits:
- Adding to the end becomes O(1) instead of O(n)
- No need to traverse the entire list to find the last node
Trade-offs:
- Need to maintain both references during add/remove operations
- Slightly more complex code
Linked Implementation of Stack ADT
Stacks are perfect for linked implementation because we only ever add/remove from one end!
Design Decision: Which End is the Top?
Smart Choice: Make the first node represent the top of the stack.
Why? Because adding/removing from the beginning of a linked list is O(1) - exactly what we want for push/pop operations!
Visual Reference (Slide 60):
topNode β [Top|β’] β [Middle|β’] β [Bottom|null]
β
Stack conceptually
Push Operation
Visual Reference (Slide 61):
Before: topNode β [A|β’] β [B|β’] β [C|null]
newNode β [NEW|null]
After: topNode β [NEW|β’] β [A|β’] β [B|β’] β [C|null]
Push Algorithm:
- Create a new node with the data
- Make the new node point to the current top
- Make
topNodepoint to the new node
This is identical to βadding to the beginningβ of a linked list!
Pop Operation
Visual Reference (Slides 63-64):
Before: topNode β [A|β’] β [B|β’] β [C|null]
After: topNode β [B|β’] β [C|null]
Return A to the client
Pop Algorithm:
- Save reference to the data in the top node
- Make
topNodepoint to the second node - Return the saved data
Stack Performance
All stack operations using linked implementation are O(1):
- Push: O(1)
- Pop: O(1)
- Peek: O(1)
- isEmpty: O(1)
This is excellent performance and one of the best use cases for linked structures!
Linked Implementation of Queue ADT
Queues are trickier because we add at one end (back) and remove from the other (front).
A. Linear Linked Implementation
Design Challenge: We need efficient access to both ends of the queue.
Solution: Use both head and tail references!
Visual Reference (Slide 69):
firstNode β [Front|β’] β [Middle|β’] β [Back|null] β lastNode
β β
Remove here Add here
Enqueue (Adding to Back)
Case 1: Empty Queue
Visual Reference (Slide 70):
Before: firstNode β null lastNode β null
newNode β [Data|null]
After: firstNode β [Data|null] β lastNode
Case 2: Non-empty Queue
Visual Reference (Slide 71):
Before: firstNode β [A|β’] β [B|β’] β [C|null] β lastNode
newNode β [NEW|null]
After: firstNode β [A|β’] β [B|β’] β [C|β’] β [NEW|null] β lastNode
Enqueue Algorithm:
- Create new node
- If queue is empty: make both firstNode and lastNode point to new node
- Otherwise: make current last node point to new node, then update lastNode
Dequeue (Removing from Front)
Case 1: Queue with Multiple Elements
Visual Reference (Slide 73):
Before: firstNode β [A|β’] β [B|β’] β [C|null] β lastNode
After: firstNode β [B|β’] β [C|null] β lastNode
Return A to client
Case 2: Queue with One Element
Visual Reference (Slide 74):
Before: firstNode β [Data|null] β lastNode
After: firstNode β null lastNode β null
Return Data to client
Dequeue Algorithm:
- Save reference to front nodeβs data
- Update firstNode to point to second node
- If queue becomes empty: set lastNode to null too
- Return saved data
B. Circular Linked Implementation
Cool Idea: What if the last node points back to the first node instead of null?
Visual Reference (Slide 80):
βββββββββββββββββββββββ
β β
[A|β’] β [B|β’] β [C|β’] βββββ
β β
first lastNode
Benefits:
- Only need ONE reference (to the last node)
- Can still find the first node via
lastNode.next - Slightly more memory efficient
Trade-offs:
- More complex logic for edge cases
- Need to be careful about infinite loops during traversal
C. Doubly Linked Implementation
Advanced Concept: Each node has TWO references - one to the next node and one to the previous node.
Visual Reference (Slide 83):
firstNode β [β’|A|β’] β [β’|B|β’] β [β’|C|β’] β lastNode
Benefits:
- Can traverse in both directions
- Easier to remove nodes (donβt need to find the previous node)
- More flexible for complex operations
Trade-offs:
- Uses more memory (extra reference per node)
- More complex maintenance code
Efficiency Comparisons and Trade-offs
When to Use Linked Structures
Great for:
- Frequent insertions/deletions at the beginning
- Data that grows and shrinks unpredictably
- When you donβt need random access to elements
- Stack and queue implementations
Not ideal for:
- Applications requiring frequent random access (getting element at position i)
- When memory usage is extremely tight
- Mathematical operations requiring indexed access
Memory Considerations
Linked Structures:
- Use more memory per element (need space for references)
- Memory is allocated as needed (no waste)
- Memory may be scattered throughout RAM
Arrays:
- Less memory per element (just the data)
- May have unused allocated space
- Memory is contiguous (better cache performance)
π Connections and Review
Letβs tie everything together with the key insights:
The Big Innovation: Linked structures solve the fundamental limitations of arrays by trading direct access for dynamic flexibility.
The Core Pattern: Almost every operation on linked structures follows the same basic steps:
- Create nodes as needed
- Traverse to find the right position
- Adjust references to maintain connections
- Update head/tail references as needed
Implementation Strategy:
- Lists: Use head reference, optionally add tail reference for efficiency
- Stacks: Use head reference as top (perfect match!)
- Queues: Need both head and tail references for efficiency
Performance Rule of Thumb:
- If you need lots of random access: choose arrays
- If you need lots of insertion/deletion: choose linked structures
- If youβre implementing stacks/queues: linked structures are often ideal
Real-World Applications:
- Web browsers use linked structures for browser history
- Music players use them for playlists
- Operating systems use them for process management
- Graphics programs use them for undo/redo functionality
The beauty of linked structures is that once you understand the basic concept of nodes and references, you can build incredibly flexible and powerful data structures that adapt to your programβs needs in real-time. Theyβre a perfect example of how a simple idea (connecting things with pointers) can solve complex problems!