Deques & Priority Queue ADTs
Double-ended queues, sliding window maximums, priority queue abstract contracts, and comparing array vs heap vs BST implementations.
Reinforce your mental model: run, pause, and inspect live pointers with our interactive visualizer:
Topics Covered:
33. Double-Ended Queue (Deque) ADT & Core Operations • Input-Restricted vs Output-Restricted Deques • Circular Array Deque Modulo Mathematics • The Sliding Window Maximum Monotonic Deque Pattern (Potential Method Proof) • 34. Priority Queue ADT Fundamentals & Underlying Data Structure Trade-offs • Production Implementations
Linear data structures culminate in two powerful generalized container models: Double-Ended Queues (Deques) and Priority Queues. While a Deque generalizes sequence boundaries by enabling constant-time insertions and removals at both the front and rear, a Priority Queue breaks away from chronological arrival ordering entirely, servicing elements based on an intrinsic priority key. This chapter formalizes bidirectional ring buffer mathematics, monotonic deque algorithms for optimal sliding window queries, the Priority Queue abstract contract, and comparative trade-offs across contiguous, linked, and tree-backed implementations.
Learning Objectives
#- Formalize the Double-Ended Queue (Deque) ADT contract and demonstrate how it subsumes both LIFO stacks and FIFO queues.
- Implement circular array deques using bidirectional modular arithmetic to step backward and forward in physical RAM without shifting.
- Formulate the decreasing monotonic deque invariant and prove how it solves the Sliding Window Maximum problem in optimal time using the Physicist's Potential Method.
- Define the Priority Queue ADT interface and compare the asymptotic bounds of arrays, linked lists, balanced binary search trees, and binary heaps.
- Trace real-world deployments in operating system scheduling (Linux CFS), network traffic QoS packet prioritization, and Huffman tree construction.
Topic 33: Double-Ended Queue (Deque)
#1. Conceptual Architecture & Dual-Boundary Access
A Double-Ended Queue (Deque), pronounced "deck", is a generalized linear container that permits element insertion and deletion with equal efficiency at both ends: the Front and the Rear.
Because access is permitted at both boundaries, the Deque serves as a universal linear primitive:
- Restricting mutations to
PushFrontandPopFrontforms a LIFO Stack. - Restricting mutations to
PushBackandPopFrontforms a FIFO Queue.
2. The Deque Abstract Data Type (ADT) Interface
#| Operation | Description | Target Time | Auxiliary Space | Boundary Condition / Check |
|---|---|---|---|---|
PushFront(x) | Prepends element at the Front | Fails if bounded capacity is saturated | ||
PushBack(x) | Appends element at the Rear | Fails if bounded capacity is saturated | ||
PopFront() | Removes and returns element at Front | Fails with Underflow if deque is empty | ||
PopBack() | Removes and returns element at Rear | Fails with Underflow if deque is empty | ||
PeekFront() | Inspects front-most element without removal | Requires non-empty deque | ||
PeekBack() | Inspects rear-most element without removal | Requires non-empty deque | ||
IsEmpty() | Returns true if size is 0 | Verified via count == 0 | ||
IsFull() | Returns true if count equals capacity | Verified via count == capacity |
3. Bidirectional Modular Arithmetic in Circular Deques
#To achieve time across all four boundary operations without dynamic node allocations or memory shifting, an implementation wraps a contiguous array using modular arithmetic in both directions:
Advancing Forward (PushBack, PopFront):
Stepping Backward (PushFront, PopBack):
Adding Capacity before modulo ensures the intermediate value remains strictly non-negative in languages where % calculates truncated remainder rather than Euclidean modulo:
4. Algorithmic Mastery: Sliding Window Maximum via Monotonic Deque
#The Problem:
Given an array of numbers and a sliding window of size , find the maximum value in every window as it slides from left to right.
- Brute Force: Inspecting all elements per window requires time.
- Monotonic Deque Solution: Achieves optimal linear time by maintaining a strictly decreasing invariant!
Formal Amortized Analysis via the Physicist's Potential Method:
Define the potential function at step as the number of elements currently stored in the deque:
- For each index :
- Let be the number of elements popped from the back (dominated elements) plus elements popped from the front (expired elements).
- The actual work performed is (one push plus pops).
- The change in potential is:
- The amortized cost per element is:
Across the entire array of length , total operations Strictly linear time.
Topic 34: Priority Queue Fundamentals & Trade-offs
#1. The Priority Queue Abstract Data Type (ADT)
#A Priority Queue is an Abstract Data Type where element servicing is governed not by chronological arrival order, but by an associated Priority Key:
- Max-Priority Queue: The element with the highest key is extracted first (
ExtractMax). - Min-Priority Queue: The element with the lowest key is extracted first (
ExtractMin).
[!CONCEPT] Hospital Triage Mental Model Incoming Patients:
- Patient A: Arrival 8:00 AM, Mild Flu Priority 2
- Patient B: Arrival 8:15 AM, Acute Trauma Priority 10
Next Serviced: Patient B (Priority 10), superseding Patient A regardless of arrival time!
2. Architectural Comparison of Underlying Implementations
#| Underlying Storage Architecture | Insert(x, p) | Peek() | Extract() | Memory Overhead | Practical Systems Evaluation |
|---|---|---|---|---|---|
| Unsorted Array | Fast insert, unacceptably slow extract | ||||
| Sorted Array | Expensive insertion shift cascade | ||||
| Unsorted Linked List | Poor cache locality, slow scan | ||||
| Sorted Linked List | Linear traversal to find insertion point | ||||
| Balanced BST (AVL / Red-Black) | High pointer and rebalancing overhead | ||||
| Binary Heap (Complete Tree in Array) | The Gold Standard: Zero pointer overhead, contiguous array storage, maximum L1 cache efficiency |
3. Production Multi-Language Implementations
#A. C++20 Optimal Sliding Window Maximum Monotonic Deque
#include <vector>
#include <deque>
#include <span>
std::vector<int> maxSlidingWindow(std::span<const int> nums, int k) {
if (nums.empty() || k <= 0) return {};
std::deque<int> dq; // Stores indices of candidate maximums
std::vector<int> result;
result.reserve(nums.size() - k + 1);
for (int i = 0; i < static_cast<int>(nums.size()); ++i) {
// 1. Evict expired index outside current window [i - k + 1, i]
if (!dq.empty() && dq.front() <= i - k) {
dq.pop_front();
}
// 2. Preserve strictly decreasing monotonic invariant
while (!dq.empty() && nums[dq.back()] <= nums[i]) {
dq.pop_back();
}
// 3. Insert current element's index
dq.push_back(i);
// 4. Record maximum once the first window is primed
if (i >= k - 1) {
result.push_back(nums[dq.front()]);
}
}
return result;
}B. Python 3 Monotonic Deque with Type Annotations
from collections import deque
from typing import List
def max_sliding_window(nums: List[int], k: int) -> List[int]:
"""Finds maximum in each sliding window of size k in O(n) linear time."""
if not nums or k <= 0:
return []
dq: deque[int] = deque() # Stores indices
result: List[int] = []
for i, num in enumerate(nums):
# 1. Evict expired index
if dq and dq[0] <= i - k:
dq.popleft()
# 2. Maintain decreasing monotonic invariant
while dq and nums[dq[-1]] <= num:
dq.pop()
# 3. Add current index
dq.append(i)
# 4. First window completes at index k - 1
if i >= k - 1:
result.append(nums[dq[0]])
return result4. Key Takeaways
#- Deque Generalization: Deques support bidirectional operations at both
FrontandRear, seamlessly subsuming both stacks and queues. - Bidirectional Modulo: Stepping backward in a circular array deque requires
(idx - 1 + capacity) % capacityto prevent negative dividend truncation. - Monotonic Deques: Enforcing a strictly decreasing invariant across stored indices eliminates dominated candidates, solving the Sliding Window Maximum problem in optimal time.
- Priority Queue ADT: Governed by priority rankings rather than arrival time; the Binary Heap is the canonical backing implementation due to extraction and zero pointer overhead.
Academic Attribution & References
#- Cormen, T. H., Leiserson, C. E., Rivest, R. L., & Stein, C. (2022). Introduction to Algorithms (4th ed.), Chapter 6: Heapsort, Chapter 10: Elementary Data Structures. MIT Press.
- Knuth, D. E. (1997). The Art of Computer Programming, Volume 1: Fundamental Algorithms (3rd ed.), Section 2.2: Linear Lists. Addison-Wesley.
- Sedgewick, R., & Wayne, K. (2011). Algorithms (4th ed.), Section 2.4: Priority Queues. Addison-Wesley.
- Huffman, D. A. (1952). A Method for the Construction of Minimum-Redundancy Codes. Proceedings of the IRE, 40(9), 1098-1101.