Binary search trees that keep their height under insertion and deletion. In competitive programming you rarely write one — std::set and std::map are red-black trees — but knowing the trade-offs matters, and sometimes you need one you can modify.

The family

TreeBalance ruleHeightNotes
AVLsubtree heights differ by strictest; fastest lookups, more rotations
Red-blackno two consecutive red nodes; equal black-heightwhat std::set and std::map use
Splaymove the accessed node to the rootamortized self-adjusting; basis of link-cut trees
Treaprandom priorities form a heapexpected easiest to write, supports split/merge
Scapegoatrebuild any subtree that becomes too unbalancedamortized no per-node balance data
B-tree / B+ treemany keys per nodefor disk and cache; every database uses one
WBLT / weight-balancedsubtree sizes bounded ratiosupports order statistics naturally

Which to write yourself

If you need a balanced BST you can extend — with subtree aggregates, lazy propagation, split and merge — write a treap. It is 40 lines, the balancing is a coin flip, and split/merge make range operations trivial.

Splay trees are the alternative when you need the access locality property or are building link-cut trees. AVL and red-black trees are worth understanding but almost never worth implementing.

What the STL gives you

set<int> s;                       // ordered, unique
multiset<int> ms;                 // ordered, duplicates
map<int,int> m;                   // ordered key -> value
 
s.insert(x);
s.erase(s.find(x));               // erase ONE occurrence (erase(x) removes ALL in a multiset)
auto it = s.lower_bound(x);       // first >= x
auto it2 = s.upper_bound(x);      // first > x
if (it != s.begin()) --it;        // predecessor

Two classic bugs

  1. multiset::erase(value) erases every copy. Use erase(find(value)) to remove one.
  2. std::lower_bound(s.begin(), s.end(), x) on a set is — the iterators are not random access. Always use the member function s.lower_bound(x).

Order statistics — what std::set cannot do

std::set has no “-th smallest” or “rank of ”. Three options:

  1. PBDS `tree` — a GNU extension giving find_by_order and order_of_key in . One #include and two using lines.
  2. BIT over compressed values — if the value set is known in advance, this is faster and portable.
  3. Treap with subtree sizes — if you need arbitrary extra aggregates too.

Implicit-key BSTs — treating a BST as an array

A treap keyed by position rather than value (an implicit treap) supports operations no array can:

OperationCost
Insert / erase at any position
Reverse a subrange with a lazy flag
Cyclic shift a subrange
Move a subrange elsewhere
Range aggregate and range update
Split into two sequences / concatenate

This is the structure to reach for when a problem does things to an array that a segment tree cannot express — insertion, deletion or reordering.

Rope

A balanced BST over string chunks, giving concatenation, splitting and insertion into a string. GNU C++ ships one as __gnu_cxx::rope. Rarely needed, but it is the right answer for “build a string with many insertions in the middle”.

Complexity summary

All the balanced variants give for search, insert and delete. They differ in the constant, in the extra data per node, and in which additional operations they support cheaply — split/merge (treap, splay), order statistics (any with subtree sizes), and access locality (splay).

See also: Treap · Ordered Set / PBDS · General Data Structures