Stacks & Queues
Overview
Stacks and queues are fundamental linear data structures that provide restricted access to elements. They are widely used in algorithms, system design, and real-world software applications such as parsers, task scheduling, and breadth/depth-first search.
In Swift, stacks and queues can be implemented using arrays or linked lists, depending on performance needs.
Stacks
A stack is a Last-In-First-Out (LIFO) structure. Think of it like a stack of plates: you add to the top and remove from the top.
Basic Operations
push(_:) – Add to the top
pop() – Remove from the top
peek() – View the top element without removing it
Stack Implementation Using Array
struct Stack<T> {
private var elements: [T] = []
var isEmpty: Bool {
return elements.isEmpty
}
var count: Int {
return elements.count
}
mutating func push(_ value: T) {
elements.append(value)
}
mutating func pop() -> T? {
return elements.popLast()
}
func peek() -> T? {
return elements.last
}
}
Example Usage
var stack = Stack<Int>()
stack.push(10)
stack.push(20)
stack.pop() // 20
stack.peek() // 10
Queues
A queue is a First-In-First-Out (FIFO) structure. Like a line at a store, the first person to enter is the first to be served.
Basic Operations
enqueue(_:) – Add to the rear
dequeue() – Remove from the front
peek() – View the front element without removing it
Queue Implementation Using Array
struct Queue<T> {
private var elements: [T] = []
var isEmpty: Bool {
return elements.isEmpty
}
var count: Int {
return elements.count
}
mutating func enqueue(_ value: T) {
elements.append(value)
}
mutating func dequeue() -> T? {
return isEmpty ? nil : elements.removeFirst()
}
func peek() -> T? {
return elements.first
}
}
Queue Implementation Using Two Stacks
struct EfficientQueue<T> {
private var input: [T] = []
private var output: [T] = []
mutating func enqueue(_ value: T) {
input.append(value)
}
mutating func dequeue() -> T? {
if output.isEmpty {
output = input.reversed()
input.removeAll()
}
return output.popLast()
}
func peek() -> T? {
return output.isEmpty ? input.first : output.last
}
var isEmpty: Bool {
return input.isEmpty && output.isEmpty
}
}
Performance Comparison
Operation |
Stack (Array) |
Queue (Array) |
Queue (2 Stacks) |
---|---|---|---|
Push |
O(1) |
O(1) |
O(1) |
Pop/Dequeue |
O(1) |
O(n) |
Amortized O(1) |
Peek |
O(1) |
O(1) |
O(1) |
Use Cases
Stack
Undo/redo operations
Syntax parsing (e.g., balanced parentheses)
Function call stack
Depth-first search
Queue
Task scheduling
Breadth-first search
Buffers (e.g., print/spool queue)
Order processing systems
Best Practices
Use
struct
for value semantics.Prefer specialized structures (like
EfficientQueue
) when performance matters.Ensure thread-safety in concurrent environments (e.g., with
DispatchQueue
or locks).Avoid
removeFirst()
on large arrays if performance is critical.
Conclusion
Stacks and queues are essential tools in a developer’s toolbox. While Swift’s array type can be used to implement both structures easily, it’s important to understand their time complexity and performance implications.
In the next chapter, we’ll dive into Trees, starting with binary trees and traversals, and explore how they are used in decision-making and hierarchical data modeling.