Chapter 07

Linked Lists, Stacks and Queues

Linked-list structures and operations, followed by stack and queue data structures, their algorithms, applications, and abstract data types.

7.1Linked Lists

Linked list

A linked list is a linear data structure made of nodes. Each node stores data and a pointer to the next node.

  • Its size is dynamic.
  • It does not require contiguous memory locations.
  • Types include singly, doubly, and circular linked lists.

7.2Node Structure

Each node has two parts:

  • Data — the value stored in the node.
  • Pointer — a reference to the next node.
Step by step: node structure
Build the data field, pointer field, link, and final None pointer.

7.3Singly Linked List

A singly linked list can be traversed in one direction. The head points to the first node, and the last node’s next pointer is None.

Step by step: singly linked list
Follow head and each next pointer from 10 to 40.

7.4Insertion at the Front

  1. Create a new node.
  2. Point its next to the current head.
  3. Update the head to the new node.
def insert_front(self, data):
    new_node = Node(data)
    new_node.next = self.head
    self.head = new_node
Step by step: insert at the front
The visual trace follows the three pointer updates from the slide.

7.5Insertion in the Middle

  1. Traverse to the node just before the desired position.
  2. Point the new node’s next to the current node’s next.
  3. Point the current node’s next to the new node.
def insert_middle(self, position, data):
    if position < 0:
        print("Invalid position.")
        return

    new_node = Node(data)
    if position == 0:
        new_node.next = self.head
        self.head = new_node
        return

    temp = self.head
    for _ in range(position - 1):
        if temp is None:
            print("Position out of bounds.")
            return
        temp = temp.next

    if temp is None:
        print("Position out of bounds.")
        return

    new_node.next = temp.next
    temp.next = new_node
Step by step: insert in the middle
The temporary pointer temp keeps the address of node 20 while both links are updated.
head temp new
10
20
new
30
40
Step 1 of 6
Temp register temp → node 10
temp = self.head

7.6Insertion at the End

  1. Traverse to the last node.
  2. Update the last node’s next to the new node.
while last.next:
    last = last.next
last.next = new_node
Step by step: insert at the end
Move current to the last node, then link it to the new node.

7.7Insertion in a Singly Linked List

class Node:
    def __init__(self, data):
        self.data = data
        self.next = None


class LinkedList:
    def __init__(self):
        self.head = None

    def insert_at_end(self, data):
        new_node = Node(data)

        # If list is empty
        if self.head is None:
            self.head = new_node
            return

        # Traverse till the last node
        current = self.head
        while current.next is not None:
            current = current.next

        # Insert at the end
        current.next = new_node

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


if __name__ == "__main__":
    ll = LinkedList()
    ll.insert_at_end(10)
    ll.insert_at_end(20)
    ll.insert_at_end(30)
    ll.insert_at_end(40)
    ll.traverse()
Output 10 -> 20 -> 30 -> 40 -> None

7.8Deletion in a Singly Linked List

Delete from the front

  • Update the head to the next node.
  • The old head becomes unreachable and can be reclaimed.
def delete_front(self):
    if self.head is None:
        return
    self.head = self.head.next

Delete from the middle

  • Traverse to the previous node of the target node.
  • Update its next to skip the target node.
def delete_key(self, key):
    if self.head is None:
        return False
    if self.head.data == key:
        self.head = self.head.next
        return True

    temp = self.head
    while temp.next and temp.next.data != key:
        temp = temp.next
    if temp.next is None:
        return False
    temp.next = temp.next.next
    return True

Delete from the end

  1. Traverse to the second-last node.
  2. Update its next to None.
def delete_end(self):
    if self.head is None:
        return
    if self.head.next is None:
        self.head = None
        return

    temp = self.head
    while temp.next.next:
        temp = temp.next
    temp.next = None
Time complexity Deleting the front node is O(1). Deleting by key or deleting the last node is O(n) because the list may need to be traversed.

7.9Doubly and Circular Linked Lists

Doubly linked list

  • Each node contains data, a next pointer, and a previous pointer.
  • It allows traversal in both directions.
  • The first node’s previous pointer is None, and the last node’s next pointer is None.
class DoublyNode:
    def __init__(self, data):
        self.data = data
        self.next = None
        self.prev = None
Example: insert X between B and C
Update all four pointers required for insertion in the middle of a doubly linked list.
None None temp
head A
B
new X
C
D
Step 1 of 7 temp = node B

Circular linked list

  • It is similar to a singly linked list, but the last node’s next points back to the head.
  • There are no None values in the list.
  • It can be singly or doubly circular.
Step by step: circular linked list
Link 1 to 2, 2 to 3, and the last node back to head.

7.10Advantages, Disadvantages and Exercises

Advantages
  • Dynamic size.
  • Efficient memory utilization.
Disadvantages
  • Extra memory is required for pointers.
  • Elements are accessed sequentially.

Exercises

  1. Implement all singly linked-list operations.
  2. Reverse a singly linked list without recursion.
  3. Reverse a singly linked list using recursion.

Worked trace: reverse 1 → 2 → 3 using the call stack

This version first recurses to the end of the list. The base case makes the last real node the new head; the suspended assignments then run in reverse frame order—F3, F2, and F1—to redirect the links. Nothing is returned: the method changes self.head and the existing nodes in place.

1def reverse(self, prev, cur):
2 if cur:
3 self.reverse(cur, cur.next)
4 cur.next = prev
5 else:
6 self.head = prev
7self.reverse(None, self.head)
1 → 2 → 3 → None
prevNone
curnode 1
executes nextline 2
Line 7 calls reverse(None, node 1). Frame F1 is pushed; line 2 executes next.
Step 1 of 13

7.11Stack Data Structure

Stack

A stack is a linear data structure that follows the LIFO principle: Last In, First Out.

Elements are added and removed from the same end, called the top.

7.12Stack Operations

  • Push — add an element to the top of the stack.
  • Pop — remove and return the top element of the stack.
  • Peek — view the top element without removing it.
  • IsEmpty — check whether the stack is empty.
  • IsFull — check whether the stack is full for a fixed-size stack or array implementation.
Stack: push one element at a time, then pop
Begin with an empty stack, insert 15, 6, 2, 9, 17, and 3, then remove the top element.
operation: start
S.top = 0
capacity: 7

7.13Stack Operations: Algorithms

PUSH(S, x)

if S.top == S.size
    error(overflow)
else S.top = S.top + 1
    S[S.top] = x

POP(S)

if STACK-EMPTY(S)
    error(underflow)
else S.top = S.top - 1
    return S[S.top + 1]

STACK-EMPTY(S)

if S.top == 0
    return TRUE
else return FALSE
Notation

S.top points to the top element. S.size is the maximum size of the stack.

Exercises

  1. Reverse a string using a stack implemented with an array.
  2. Evaluate a postfix expression, for example 5 2 3 * +.
  3. Check balanced parentheses using a stack implemented with an array.

7.14Queue Data Structure

Queue

A queue is a linear data structure that follows the FIFO principle: First In, First Out.

  • Elements are added and removed from both ends.
  • Elements are added at the rear (tail).
  • Elements are removed from the front (head).

7.15Queue Operations

  • Enqueue — add an element at the rear.
  • Dequeue — remove an element from the front.
  • Peek/Front — retrieve the front element without removing it.
  • IsEmpty — check whether the queue is empty.
  • IsFull — check whether the queue is full for an array implementation.
Queue: enqueue one element at a time, then dequeue
Begin empty, enqueue 2 through 8, remove 2 from the front, then enqueue 9.

7.16Circular Queue Operations

In a circular queue, location 1 immediately follows location n: the queue wraps around.

Circular queue: start empty and enqueue one by one
Begin with head = tail = 7, build the slide’s queue, wrap around, then dequeue 15.
Queue is empty Q.head = Q.tail
After enqueuing one element Q.head = Q.head
Q.tail = Q.head + 1
Queue is full Q.head = Q.tail + 1
or
(Q.head = 1 and Q.tail = Q.size)

7.17Queue Applications and Abstract Data Types

Applications of queues

  • CPU scheduling and disk scheduling.
  • Buffer management.
  • Handling packets in routers.
  • Ticketing queues.

Abstract Data Types

ADT

An abstract data type describes the logical behavior of a data structure without specifying its implementation. It defines what operations are available, not how they are implemented.

Abstract data typePrincipleOperations
StackLIFOpush, pop, peek
QueueFIFOenqueue, dequeue, peek