5 Stacks and Queues
A practical guide to stacks, queues, deques, their implementations, complexity trade-offs, and common algorithmic applications.
behavior and operations
A is a linear abstract data type whose removal rule is last in, first out (). If A, B, and C are pushed in that order, C is removed first, followed by B, then A.
The core operations are:
push(x): insertxat the top.pop(): remove and return the top element.peek()ortop(): inspect the top element without removing it.isEmpty(): test whether the structure has no elements.size(): return the number of elements.
Removing from or inspecting an empty causes . A fixed-capacity array can also experience overflow when an insertion is attempted after the array is full.
Takeaway: choose a when the newest pending item should be handled first, such as in undo histories, nested operations, parsing, or backtracking.
behavior and operations
A ordinarily follows first in, first out (). If A, B, and C are enqueued in that order, A is removed first, followed by B, then C. New elements enter at the rear, and removals occur at the front.
The core operations are:
enqueue(x): insertxat the rear.dequeue(): remove and return the front element.front()orpeek(): inspect the front element without removing it.isEmpty(): test whether the structure has no elements.size(): return the number of elements.
An empty produces when a removal or inspection is attempted. A bounded may also report overflow when it has no unused capacity.
A is appropriate when older work should be served before newer work, including print jobs, processor tasks, packets, and requests waiting for service.
Takeaway: the removal rule distinguishes the structures: a favors the newest item, while a ordinarily favors the oldest item.
Array implementations and circular storage
An array-based stores elements near one end of an array and maintains a top position. If top denotes the next unused position, insertion writes at items[top] and then increments top; removal decrements top and returns the element at the new position.
With sufficient capacity, push, pop, and peek each take time. A dynamic array may occasionally resize by allocating a larger array and copying existing elements. One resize can take time, but geometric growth makes a long sequence of insertions per insertion. Storing elements requires space.
A naive array removes the front element by shifting every remaining element toward the front. That makes one dequeue take time. The usual improvement is a : maintain a front index, a rear index, and a size count, and advance each index using modular wraparound.
With a fixed-capacity , enqueue, dequeue, and peek take time. Dynamic growth can make an individual insertion expensive, but repeated insertions remain amortized constant time. The uses space.
Takeaway: arrays give compact storage and good locality, but queues must avoid shifting elements after every dequeue.
Linked-list implementations
A linked-list implementation stores each element in a node containing a value and one or more links. Nodes do not need to occupy adjacent memory locations.
For a linked-list , the list head serves as the top. A push creates a node, links it before the current head, and updates the head. A pop removes the head and advances the pointer. Therefore, push, pop, and peek each take time.
For a linked-list , maintain both a front pointer and a rear pointer. Enqueueing attaches a new node after the rear and moves the rear pointer. Dequeueing removes the front node and advances the front pointer. When the becomes empty, both pointers must be reset appropriately. With both endpoint pointers, enqueue, dequeue, and peek take time.
If a singly linked omits its rear pointer, finding the insertion point requires traversal, making enqueue take time. Linked structures use space but include node and pointer overhead.
Takeaway: linked lists grow one node at a time, while endpoint pointers are essential for constant-time operations.
Complexity and implementation trade-offs
The abstract data type specifies behavior, while the implementation determines how that behavior is achieved and what it costs.
For a well-designed or :
Primary insertion, removal, and next-element inspection operations should take time.
isEmptyandsizetake time when a size counter is maintained.Traversing all stored elements takes time.
Searching for an arbitrary value in an unsorted structure takes time.
Storing elements takes space.
The phrase is important because dynamic arrays sometimes resize. The copying cost of a resize is spread across many successful insertions rather than charged equally to every individual operation.
Implementation choice also affects practical performance. Arrays often provide better locality of reference and lower per-element overhead. Linked lists can be useful when the maximum size is unknown or when nodes must be added without relocating other elements. Java's ArrayDeque is a resizable-array whose ordinary end operations are amortized constant time, and Java recommends a implementation rather than the legacy class for behavior.
Deques and applications
A supports insertion, removal, and inspection at both ends. behavior uses insertion at the rear and removal from the front. behavior inserts and removes at the same end. A also permits operations such as inserting at the front or removing from the rear.
Stacks and queues appear throughout algorithms and systems:
A tracks active function calls. Calling a function pushes an activation record, and returning pops it. Nested calls therefore return in the reverse order in which they were created. Deep recursion can exhaust the .
Expression processors use stacks to match parentheses and brackets, evaluate postfix expressions, and manage operators and operands. For
([{}]), each opening delimiter is pushed, and each closing delimiter must match the most recently pushed opening delimiter.Undo systems store earlier actions or states on a . Backtracking stores choices and removes them when a choice fails.
Depth-first search can use an explicit instead of recursion.
Scheduling and buffering systems use queues for work awaiting service.
uses a to process graph vertices in layers. With an adjacency-list graph and suitable bookkeeping, its running time is , where is the number of vertices and is the number of edges.
Use a for nested operations, undo history, parsing, depth-first exploration, and backtracking. Use a for fair processing, buffering, scheduling, producer-consumer pipelines, and breadth-first exploration. Use a when both ends must be accessed, and use a priority when priority rather than arrival order determines service.
Final takeaway: the correct structure follows the required removal rule: for stacks, ordinarily for queues, and both-end access for deques.