Chapter 09
Linked stacks and queues
In this chapter
Learning outcomes
Use linked-list operations to implement stack and queue ADTs, and preserve endpoint invariants through empty transitions.
9.1 Linked stack
Let top point to the first node. Push is insertion at the head; pop is deletion at the head. Both perform a constant number of pointer updates. A push also requires allocation, which can fail. “No fixed capacity” is not the same as unlimited memory.
PUSH(x):
allocate new node; if allocation fails, report failure
new.value = x
new.next = top
top = new
POP():
if top is NULL: report underflow
old = top
result = old.value
top = old.next
free old
return result
After pushing 10, 20 and 30, top points to 30 → 20 → 10 → NULL. Popping returns 30 and leaves 20 → 10 → NULL. The operation must save the returned value before freeing its node.
The pointer-update cost is Θ(1); the cost of the system allocator is normally abstracted away in this introductory analysis. Displaying all elements is Θ(n), and total node storage is Θ(n). If constant-time size is required, maintain a count rather than traversing for every size query.
9.2 Linked queue
A queue retains two endpoints: front points to the next item to remove, and rear points to the last node. Enqueue at rear and dequeue at front.
Invariant: an empty queue has both front and rear null. A nonempty queue has a reachable rear node whose next link is null.
ENQUEUE(x):
allocate new node; if allocation fails, report failure
new.value = x; new.next = NULL
if rear is NULL: front = new
else: rear.next = new
rear = new
DEQUEUE():
if front is NULL: report underflow
old = front; result = old.value
front = old.next
if front is NULL: rear = NULL
free old
return result
The reset of rear is essential. Without it, removing the final node leaves a dangling rear pointer, and a later enqueue may write through freed storage.
9.3 Worked boundary trace
| Operation | Front | Rear | Sequence |
|---|---|---|---|
| Start | NULL | NULL | Empty |
| Enqueue 10 | node 10 | node 10 | 10 |
| Enqueue 20 | node 10 | node 20 | 10, 20 |
| Dequeue | node 20 | node 20 | 20 |
| Dequeue | NULL | NULL | Empty |
| Enqueue 30 | node 30 | node 30 | 30 |
Each enqueue or dequeue performs Θ(1) link work. No shifting or modular arithmetic is needed, but node links and allocations add overhead. For a bounded buffer with predictable memory use, a circular array may be the better choice.
9.4 Design the API around failure
Returning a special integer such as −1 is ambiguous if −1 is valid data. A boolean success result plus an output pointer separates status from value. The output remains unchanged on failure. Document pointer validity as a precondition and define who owns each allocated node.
Test an empty pop, a full lifecycle from empty to populated and back, repeated reuse after emptying, and cleanup with remaining elements. Allocation-failure testing requires a controllable allocator or fault-injection wrapper; ordinary successful runs do not prove that path was executed.
Practice
- Show the linked stack after pushing 7, pushing 9, popping, and pushing 4.
- Explain why a linked queue with only a front pointer cannot generally enqueue in Θ(1) time.
- Identify the unsafe statement if code frees old before evaluating
old->next. - Write a queue cleanup algorithm and state its final endpoint invariant.
Programming laboratory
Download linked_stack_queue.c · Contracts and run instructions
Compile this file on its own. Check the empty, boundary and ordinary cases before changing the implementation.
Read the complete C11 program
/* C11. Linked stack and queue with explicit allocation and ownership. */
#include <assert.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
typedef struct Node { int value; struct Node *next; } Node;
typedef struct { Node *top; } Stack;
typedef struct { Node *front, *rear; } Queue;
static Node *make_node(int value)
{
Node *p = malloc(sizeof *p);
if (p != NULL) *p = (Node){value, NULL};
return p;
}
static bool push(Stack *s, int value)
{
Node *p = make_node(value);
if (p == NULL) return false;
p->next = s->top; s->top = p; return true;
}
static bool pop(Stack *s, int *out)
{
if (s->top == NULL) return false;
Node *p = s->top; *out = p->value; s->top = p->next; free(p); return true;
}
static bool enqueue(Queue *q, int value)
{
Node *p = make_node(value);
if (p == NULL) return false;
if (q->rear == NULL) q->front = p;
else q->rear->next = p;
q->rear = p; return true;
}
static bool dequeue(Queue *q, int *out)
{
if (q->front == NULL) return false;
Node *p = q->front; *out = p->value; q->front = p->next;
if (q->front == NULL) q->rear = NULL; /* Never leave a dangling tail. */
free(p); return true;
}
static void clear_stack(Stack *s) { int ignored; while (pop(s, &ignored)) {} }
static void clear_queue(Queue *q) { int ignored; while (dequeue(q, &ignored)) {} }
int main(void)
{
Stack s = {NULL}; Queue q = {NULL, NULL}; int value = -1;
assert(!pop(&s, &value) && !dequeue(&q, &value) && value == -1);
for (int i = 1; i <= 3; ++i) {
if (!push(&s, i) || !enqueue(&q, i)) {
clear_stack(&s); clear_queue(&q); return EXIT_FAILURE;
}
}
printf("Linked stack:");
for (int i = 3; i >= 1; --i) {
assert(pop(&s, &value) && value == i); printf(" %d", value);
}
putchar('\n');
printf("Linked queue:");
for (int i = 1; i <= 3; ++i) {
assert(dequeue(&q, &value) && value == i); printf(" %d", value);
}
putchar('\n'); assert(q.front == NULL && q.rear == NULL && s.top == NULL);
for (int i = 0; i < 100; ++i) {
assert(enqueue(&q, i)); assert(dequeue(&q, &value) && value == i);
assert(q.front == NULL && q.rear == NULL);
}
assert(push(&s, 9) && enqueue(&q, 9));
clear_stack(&s); clear_queue(&q); clear_stack(&s); clear_queue(&q);
assert(s.top == NULL && q.front == NULL && q.rear == NULL);
puts("linked stack/queue tests passed");
return EXIT_SUCCESS;
}