Treaps: Elegant Balanced Search Trees via Randomized Priorities
A Treap—a portmanteau of **Tree** and **Heap**—is a binary search tree data structure invented by Raimund Seidel and Cecilia R. Aragon in 1989. While deterministic self-balancing trees like AVL or Red-Black trees require complex rotations and invariant tracking, a Treap achieves balance by assigning each node a randomly generated priority at creation time.
By enforcing BST ordering on the keys and Heap ordering on the randomized priorities, a Treap behaves identically to a random BST built from a random permutation of inputs, guaranteeing an expected tree depth of O(log N) with minimal implementation complexity.
The Dual Invariant Properties
Every node in a Treap stores two values: a search key `x` and a numerical priority `p` (assigned uniformly at random upon creation). The structure satisfies two invariants simultaneously:
- Binary Search Tree Property: For any node u, all keys in its left subtree are strictly smaller than u.key, and all keys in its right subtree are strictly greater than u.key.
- Heap-Order Property (Max-Heap): For any node u, its priority u.priority is greater than or equal to the priorities of all its children.
- Uniqueness Property: For any given distinct set of (key, priority) pairs, there exists exactly one unique Treap topology satisfying both invariants.
Core Primitives: Split and Merge
While standard Treaps can be updated using tree rotations, modern competitive programming and advanced system designs manipulate Treaps exclusively through two primitives: `split` and `merge`.
1. Split(T, Key) -> (L, R)
Partitions a single Treap T into two independent Treaps: tree L (containing all nodes with `key <= Key`) and tree R (containing all nodes with `key > Key`). Expected Time Complexity: O(log N).
2. Merge(L, R) -> T
Combines two Treaps L and R into a unified Treap T, under the strict precondition that every key in L is smaller than every key in R. The root of T is chosen as whichever tree has the higher root priority, recursively merging the remaining subtrees. Expected Time Complexity: O(log N).
Implicit Treaps: Dynamic Arrays and Fast Interval Operations
An **Implicit Treap** replaces explicit search keys with the implicit index of each node in the array (computed dynamically as `size(node->left)`). This transforms the Treap into a flexible dynamic array (Rope) supporting:
- O(log N) Arbitrary Insertions & Deletions: Splitting at index `i`, merging the new node in between, and rebuilding the tree without moving surrounding elements in memory.
- O(log N) Range Reversals (Lazy Reversals): Applying a `lazy_flip` tag to a target range subtree, swapping left and right pointers on-demand during subsequent traversals.
- O(log N) Range Aggregations: Maintaining subtree sums, minimums, and maximums directly within node metadata.
C++ Implementation Blueprint (Split / Merge Treap)
#include <cstdlib>
#include <utility>
struct Node {
int key, priority;
Node *left = nullptr;
Node *right = nullptr;
Node(int k) : key(k), priority(std::rand()) {}
};
class Treap {
public:
// Splits tree 't' into 'l' (keys <= k) and 'r' (keys > k)
static void split(Node* t, int k, Node*& l, Node*& r) {
if (!t) {
l = r = nullptr;
} else if (t->key <= k) {
split(t->right, k, t->right, r);
l = t;
} else {
split(t->left, k, l, t->left);
r = t;
}
}
// Merges trees 'l' and 'r' into a single tree (all keys in l < keys in r)
static Node* merge(Node* l, Node* r) {
if (!l || !r) return l ? l : r;
if (l->priority > r->priority) {
l->right = merge(l->right, r);
return l;
} else {
r->left = merge(l, r->left);
return r;
}
}
static Node* insert(Node* root, int key) {
Node *l, *r;
split(root, key, l, r);
return merge(merge(l, new Node(key)), r);
}
static Node* erase(Node* root, int key) {
Node *l, *m, *r;
split(root, key - 1, l, r);
split(r, key, m, r);
delete m;
return merge(l, r);
}
};Real-World Engineering and Algorithmic Applications
- Text Editor Buffers & Rope Data Structures: Performing high-speed text insertions, cuts, and paste operations across multi-megabyte string buffers without expensive block shifts.
- Dynamic Array Segment Operations: Serving dynamic order-statistic queries and range modifications with minimal implementation overhead.
- Randomized Routing in P2P Networks: Maintaining balanced peer routing graphs using priority-based connection metrics.
- Competitive Programming Range Problems: Solving complex offline dynamic connectivity, range shifts, and interval reversal algorithms.