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:

  1. 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.
  2. 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.
  3. 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

  1. Text Editor Buffers & Rope Data Structures: Performing high-speed text insertions, cuts, and paste operations across multi-megabyte string buffers without expensive block shifts.
  2. Dynamic Array Segment Operations: Serving dynamic order-statistic queries and range modifications with minimal implementation overhead.
  3. Randomized Routing in P2P Networks: Maintaining balanced peer routing graphs using priority-based connection metrics.
  4. Competitive Programming Range Problems: Solving complex offline dynamic connectivity, range shifts, and interval reversal algorithms.