The base list of data structures: what each stores, what every operation costs, how much memory it takes, and when to reach for it. Algorithms with their time and space are in 🐬 Complexity Cheat Sheet; the decision map β€œquery pattern structure” is in Containers, Hashing and Choosing a Structure.

Memory figures were measured on 64-bit libstdc++ (GCC) by counting operator new requests for elements; node-based containers pay another 8–16 bytes of malloc bookkeeping per node on top. Costs: = amortized, = expected.

Linear structures

StructureAccess -thPush / pop backPush / pop frontInsert / erase middleSearchMemory per intIterators stable?Use when
static array, std::arrayβ€”β€”, sorted4 Byessize known at compile time; fastest
vector / 4 B (up to 8 B before shrinking: capacity doubles)no (reallocation)default container
deque (two indirections)~4.2 B (512-byte blocks + map)references yes at the ends, iterators noboth ends, or stable references while pushing
list (doubly linked) with an iterator; splice 24 B + mallocyesmany erasures/splices at known positions (rare in contests)
forward_listβ€” after an iterator16 B + mallocyesmemory-tight singly linked lists
index-based linked list (prv/nxt arrays)8 Bindices never move”delete element, find live neighbour” simulations; below
stack / queue (adapters on deque)top / front only (queue pop)β€”β€”as dequeβ€”LIFO / FIFO; use vector as a stack for speed
ring buffer (circular array)4 B, fixed capacityindices relative to headBFS queues, sliding windows with a known bound; below
monotonic stack / dequetop / endsβ€”β€”4 Bβ€”next greater element, sliding window min/max: Monotonic Stack
min-stack, min-queue (two stacks)β€” / β€”β€”min in 8 Bβ€”queue with min/max/gcd of contents; below
bitset<N>β€”β€”β€”word-parallel ops1 bitβ€”boolean DP, sets over a small universe: Bitset
vector<bool> (bit proxy)β€”β€”1 bitnolarge boolean arrays; not a real container of bool
stringfind worst1 B per char (15 chars inline: SSO)notext; hashing and automata in chapter 18

Associative structures

StructureInsert / erase / findOrdered queries (lower_bound, min, next)-th / rankMemory per int keyUse when
sorted vector + binary search insert, find4 Bstatic set: build once, query many
set / map (red-black tree)β€” ( distance)40 B (map<int,int> 40 B, map<ll,ll> 48 B)dynamic ordered set; default
multiset / multimapβ€”40 Bduplicates; erase one copy with erase(find(x))
unordered_set / unordered_map (chaining), worstnono27 B (<int,int>), 35 B (<ll,ll>)exact lookups; add a randomized hash against anti-hash tests
gp_hash_table (open addressing, pb_ds), faster in practicenono~48 B at default loadhot lookups; PBDS
pb_ds tree (order statistics)find_by_order / order_of_key 48 Brank queries on a dynamic set
treap / implicit treap~24 B (arrays)split/merge, sequence operations, reversals: Treaps
splay tree~16–24 Blink-cut trees, access locality
AVL tree worstwith sizes~24 Bstrict height ; rarely hand-written
skip listwith widths~2 pointers per key on averagesimple concurrent-friendly ordered set; below
B-tree (order ) node visitsyeswith countscompact, keys per nodeexternal memory and cache-friendly ordered sets (databases, file systems)
trie per stringlexicographic orderwith countsone node per distinct prefixprefixes, autocomplete, XOR queries: Trie, Binary Trie
radix tree (compressed trie)yeswith countsone node per branching pointlong strings with shared prefixes; suffix trees
64-ary bit tree (β€œfast set”)next / prev β€” wordsinteger keys in : Fast Set
van Emde Boas treeβ€”theory; the 64-ary tree is the practical version
Chtholly tree (interval map)amortized by assignmentsβ€”one node per runrandom β€œassign range” operations: Chtholly Tree

Priority structures

StructurePushPop minDecrease keyMeldNotes
binary heap (priority_queue)lazy deletion4 B per int; default
-ary heap is often faster (cache)
leftist / skew heap ( for skew)β€”mergeable heaps in a few lines
randomized meldable heapβ€”shortest mergeable heap code
pairing heapfast in practice; pb_ds pairing_heap_tag
binomial heapforest of binomial trees
Fibonacci heapbest bounds, poor constants; Heaps
radix heapβ€”β€”monotone integer keys (Dijkstra)
two heaps (median), heap with global addβ€”β€”running median, offset tricks

Range and tree structures (index into chapters 7, 8, 12)

StructureBuildQueryUpdateMemoryNote
prefix sums / difference array range sum (offline: range add)Prefix Sums
sparse table idempotent (min, gcd)staticSparse Table
Fenwick tree prefix sums, -th by descent pointFenwick Tree
segment tree any monoid point–Segment Tree
lazy segment tree rangeLazy Propagation
dynamic / persistent segment treeβ€” new nodesDynamic and Persistent Segment Trees
segment tree beatschmin / chmax amortized Segment Tree Beats
Li Chao treeβ€” min of lines add lineLi Chao Tree
sqrt decomposition–anything with cheap block summaries
merge sort tree / wavelet tree / -th, rankstatic / bits8. Range Queries
DSU (union by size + path compression)find union DSU; with rollback
binary lifting / LCA ( with Euler tour + RMQ)staticLCA
heavy-light decompositionpath query 12. Trees
link-cut tree / Euler-tour treeβ€” path / subtreelink, cut dynamic forests: Euler Tour Tree
centroid decompositionpath counting per vertexβ€”12. Trees
KD-tree2D orthogonal range worst case; nearest neighbour fast on typical datainsert with periodic rebuildingKD-Tree
persistent array / treap / segment treeβ€” any version new nodes per updatePersistence

Four small structures worth having

// Doubly linked list over fixed indices 0..n-1 (sentinel n): O(1) erase and neighbour queries, no allocation.
struct IndexList {
    int n; vector<int> prv, nxt;
    IndexList(int n) : n(n), prv(n + 1), nxt(n + 1) {         // starts as 0, 1, ..., n-1
        for (int i = 0; i <= n; i++) prv[i] = (i + n) % (n + 1), nxt[i] = (i + 1) % (n + 1);
    }
    void erase(int x) { nxt[prv[x]] = nxt[x]; prv[nxt[x]] = prv[x]; }
    void insertAfter(int p, int x) { prv[x] = p; nxt[x] = nxt[p]; prv[nxt[p]] = x; nxt[p] = x; }   // p = n: front
    int front() const { return nxt[n]; }                        // n means "empty" / "end"
    int next(int x) const { return nxt[x]; }
    int prev(int x) const { return prv[x]; }
};
 
template<class T> struct RingBuffer {                           // fixed capacity (power of two), O(1) at both ends
    vector<T> a; int head = 0, len = 0, mask;
    RingBuffer(int capPow2) : a(capPow2), mask(capPow2 - 1) {}
    void push_back(const T& x) { a[(head + len++) & mask] = x; }
    void push_front(const T& x) { head = (head - 1) & mask; a[head] = x; len++; }
    T pop_front() { T x = a[head]; head = (head + 1) & mask; len--; return x; }
    T pop_back() { return a[(head + --len) & mask]; }
    T& operator[](int i) { return a[(head + i) & mask]; }
    int size() const { return len; }
};
 
struct MinQueue {                                               // queue with O(1) amortized min
    vector<pair<ll, ll>> in, out;                               // (value, min of this stack up to here)
    void push(ll x) { in.push_back({x, in.empty() ? x : min(x, in.back().second)}); }
    void pop() {
        if (out.empty()) while (!in.empty()) { ll x = in.back().first; in.pop_back(); out.push_back({x, out.empty() ? x : min(x, out.back().second)}); }
        out.pop_back();
    }
    ll getMin() const {
        if (in.empty()) return out.back().second;
        if (out.empty()) return in.back().second;
        return min(in.back().second, out.back().second);
    }
    int size() const { return sz(in) + sz(out); }
};
 
struct SkipList {                                               // ordered set of ints > INT_MIN, expected O(log n)
    static const int MAXL = 24;
    vector<int> key; vector<vector<int>> nxt; int lvl = 1; mt19937 rng{20240601};
    SkipList() { key.push_back(INT_MIN); nxt.push_back(vector<int>(MAXL, -1)); }   // node 0 = head
    void findPrev(int k, int* upd) {
        int x = 0;
        for (int i = lvl - 1; i >= 0; i--) { while (nxt[x][i] != -1 && key[nxt[x][i]] < k) x = nxt[x][i]; upd[i] = x; }
    }
    bool insert(int k) {
        int upd[MAXL]; findPrev(k, upd); int y = nxt[upd[0]][0];
        if (y != -1 && key[y] == k) return false;
        int l = 1; while (l < MAXL && (rng() & 1)) l++;           // height: geometric with p = 1/2
        for (int i = lvl; i < l; i++) upd[i] = 0;
        lvl = max(lvl, l);
        int id = sz(key); key.push_back(k); nxt.push_back(vector<int>(l));
        for (int i = 0; i < l; i++) { nxt[id][i] = nxt[upd[i]][i]; nxt[upd[i]][i] = id; }
        return true;
    }
    bool erase(int k) {                                         // the node's memory is not reused
        int upd[MAXL]; findPrev(k, upd); int y = nxt[upd[0]][0];
        if (y == -1 || key[y] != k) return false;
        for (int i = 0; i < sz(nxt[y]); i++) nxt[upd[i]][i] = nxt[y][i];
        return true;
    }
    optional<int> lowerBound(int k) { int upd[MAXL]; findPrev(k, upd); int y = nxt[upd[0]][0]; if (y == -1) return nullopt; return key[y]; }
};