Treap
A treap is a randomized binary search tree data structure in which each node stores a key and a randomly assigned priority, maintaining both the binary search tree property—where keys in the left subtree are less than the node's key, and keys in the right subtree are greater—and the heap property on priorities, typically as a max-heap where a parent's priority exceeds those of its children.[1][2] This dual structure, a portmanteau of "tree" and "heap," ensures probabilistic balance without deterministic rebalancing mechanisms like rotations in AVL or red-black trees.[3]
Invented by Cecilia R. Aragon and Raimund G. Seidel in 1989, the treap draws from earlier concepts such as the Cartesian tree introduced by Jean Vuillemin in 1980 and the priority search tree by Edward M. McCreight in 1985, but uniquely employs random priorities to simulate the effects of random insertion order in binary search trees.[3][4][5] The priorities, drawn from a continuous uniform distribution, guarantee that the expected height of the tree is O(log n) for n nodes, preventing degeneration into a linear chain even under adversarial key sequences.[2][6]
Key operations on a treap, including search, insertion, and deletion, run in expected O(log n) time due to the balanced height, with insertions and deletions involving an expected constant number of rotations (specifically, fewer than two on average) to restore the heap property after temporary violations.[1][2][7] Treaps also support efficient split and merge operations in expected O(log n) time, making them suitable for applications requiring dynamic sets, such as order-statistic queries or range reporting.[1] Compared to balanced binary search trees, treaps offer simpler implementations with fewer invariants to manage, relying on randomization for performance guarantees rather than complex balancing rules.[3][6]
Introduction
Definition and Structure
A treap is a randomized binary search tree data structure that integrates the ordering properties of a binary search tree with the structural properties of a binary heap. Specifically, it is defined as a rooted binary tree where each node stores a unique key and a priority, such that the in-order traversal of the tree produces the keys in non-decreasing sorted order (satisfying the binary search tree property), and the priorities satisfy the heap property, typically a max-heap where the priority of every parent node is greater than or equal to the priorities of its children.[8][9]
The fundamental node structure in a standard treap consists of the key value, the randomly assigned priority, and pointers to the left and right child nodes. In some implementations, particularly for variants supporting order statistics or splitting/merging operations, an additional field stores the size of the subtree rooted at that node to facilitate efficient computations.[8][1]
Priorities are assigned to nodes as independent, identically distributed random values, ideally from a continuous uniform distribution to minimize the probability of ties, though in practical implementations, they are often drawn uniformly from a large discrete range such as 32-bit integers to approximate this behavior and promote expected balance.[8][10]
To illustrate, consider a small treap with keys {3, 1, 5, 2, 4} and assigned priorities {10, 20, 5, 15, 8}, where higher numbers indicate higher priority. The node with key 1 (priority 20, the maximum) becomes the root. Its left subtree is empty, while the right subtree is rooted at key 2 (priority 15), with key 3 (priority 10) as its right child; further, key 4 (priority 8) is the right child of key 3, and key 5 (priority 5) is the right child of key 4. This configuration maintains BST ordering (in-order: 1,2,3,4,5) and max-heap priorities (parent priorities exceed child priorities).[9]
Treaps are equivalent to Cartesian trees constructed over a sequence of keys with priorities serving as the secondary ordering criterion, a connection first noted in the original formulation.[8][9]
Historical Background
The treap data structure, combining the properties of a binary search tree and a binary heap, was invented by Cecilia R. Aragon and Raimund G. Seidel in 1989 while they were affiliated with the University of California, Berkeley.[4] Their work introduced randomized priorities to maintain balance in search trees, addressing the limitations of traditional binary search trees (BSTs) that could degrade to linear performance in the worst case due to unbalanced insertions or deletions.[11] Unlike explicit balancing mechanisms in structures such as AVL trees or red-black trees, which require rotations or color adjustments during operations, treaps use random priority assignments to achieve expected balance probabilistically, simplifying implementation while ensuring efficiency.[8]
Aragon and Seidel presented their invention in the paper "Randomized Search Trees" at the 30th Annual Symposium on Foundations of Computer Science (FOCS) in 1989, where they demonstrated that core operations like search, insertion, and deletion achieve O(log n) expected time complexity on average.[4] This seminal work formalized treaps—also known as randomized binary search trees—as a structure where node priorities are drawn from a continuous uniform distribution, guaranteeing high probability of logarithmic height.[11] The approach was later expanded in a full journal version published in Algorithmica in 1996, further analyzing weighted variants and performance under various distributions.
Following its introduction, treaps gained adoption in randomized algorithms research throughout the 1990s, influencing probabilistic data structures by providing a simple model for expected-case analysis in dynamic sets. In the 2000s, extensions emerged, such as implicit treaps, which adapt the structure for implicit key ordering based on subtree sizes, enabling efficient array-like operations without explicit keys.[1] Treaps served as a precursor to other probabilistic trees, including skip lists introduced by William Pugh in 1990, by popularizing randomization for balancing in search structures and inspiring hybrid probabilistic designs.
Properties and Analysis
Core Properties
A treap is defined by two fundamental invariant properties that govern its structure: the binary search tree (BST) property and the heap property. The BST property ensures that for any node in the treap, all keys in its left subtree are strictly less than the node's key, and all keys in its right subtree are strictly greater than the node's key. This ordering allows efficient searching based on keys, similar to standard binary search trees.[8] The heap property, on the other hand, imposes a partial order on randomly assigned priorities, where each parent node has a higher priority than its children, forming a max-heap (though min-heap variants exist, max-heap is conventional).[8] Together, these properties combine the ordering benefits of BSTs with the structural constraints of heaps, ensuring that the treap functions as both a search structure and a priority-ordered tree.[12]
A key implication of these invariants is the uniqueness of the treap structure for a given set of distinct keys and priorities. Specifically, the treap corresponds uniquely to a Cartesian tree constructed on the sequence of keys ordered by their priorities, where the root is the node with the maximum priority, the left and right subtrees recursively form Cartesian trees on the keys less than and greater than the root's key, respectively.[8] This uniqueness means that the tree's shape is entirely determined by the key-priority pairs, without ambiguity.[12]
The random assignment of priorities to nodes is crucial for maintaining balance, as it prevents the treap from degenerating into pathological shapes like skewed chains, which could occur in standard BSTs with adversarial inputs. With priorities drawn from a continuous distribution (e.g., uniform random reals), the resulting structure exhibits balanced properties with high probability, leading to logarithmic expected height.[8] This probabilistic balance arises inherently from the interplay of the BST and heap properties, without requiring explicit balancing mechanisms during construction.[12]
To preserve these core invariants during dynamic operations, treaps rely on structural modifications such as rotations, which adjust parent-child relationships while simultaneously upholding both the BST ordering on keys and the heap ordering on priorities. A rotation promotes or demotes a node based on its priority relative to its parent, ensuring the heap property is restored without violating the BST property, as rotations maintain in-order traversal of keys.[8] This preservation mechanism allows the treap to remain valid after insertions or deletions, reinforcing its utility as a self-balancing search tree.[12]
Treaps achieve expected O(log n) time complexity for fundamental operations such as search, insertion, and deletion, owing to the random assignment of priorities that maintains balance in expectation.[8] This performance stems from the structure's equivalence to a random binary search tree, where the expected height governs the runtime of tree traversals and modifications.[8]
A key insight into these guarantees is the analysis of the tree's height using backward analysis, which examines the probability that a given node serves as an ancestor on the path to another node. Specifically, for a node x_k in a treap with keys ordered x_1 < x_2 < \dots < x_n, the depth of x_k is the sum of indicators for whether each x_i ( i \neq k ) is an ancestor of x_k. By linearity of expectation, the expected depth is \sum_{i \neq k} \Pr[x_i \text{ is ancestor of } x_k]. A node x_i is an ancestor of x_k if and only if it has the highest priority among all nodes with keys between x_i and x_k, inclusive; since priorities are uniformly random, this probability is \frac{1}{|k - i| + 1}. Thus, the expected depth simplifies to H_k + H_{n-k+1} - 2, which is bounded by $2 \ln n + O(1), implying an expected height of O(log n).[9] This analysis also reveals that the root is uniformly random among all nodes, recursively splitting the tree into subproblems of expected size n/2, further supporting the logarithmic bound with high probability.[8]
More precise bounds confirm that the expected height E[H_n] of a treap on n nodes satisfies E[H_n] \leq 4.311 \ln n - 1.953 \ln \ln n + O(1), matching the asymptotic for random binary search trees.[13] In contrast to the worst-case O(n) height of an unbalanced binary search tree, the probability that a treap's height exceeds c log n is at most n^{1 - e^{-c \ln(c/e)}} for sufficiently large c, which is exponentially small in c.[8]
Regarding space, a treap requires O(n) storage for its nodes, each holding a key, priority, and pointers to children. Rotations during insertions and deletions occur in amortized constant time per operation, as the expected number aligns with the logarithmic path lengths.[8]
Operations on Standard Treaps
Basic Operations
Treaps support the standard binary search tree operations of searching, inserting, and deleting elements, while maintaining both the binary search tree property (based on keys) and the Cartesian tree property (based on priorities treated as a max-heap, where parent priorities exceed child priorities).[8]
Search in a treap proceeds identically to a binary search tree: starting from the root, traverse left if the target key is smaller than the current node's key, or right otherwise, until the node is found or a null pointer is reached indicating absence. The time complexity is O(h), where h is the height of the tree along the search path.[8]
Insertion begins by performing a standard binary search tree insertion, which places the new node with its key at the appropriate leaf position, temporarily ignoring priorities. A random priority is assigned to the new node upon creation. Then, to restore the heap property, the new node is rotated upward toward the root as long as its priority exceeds that of its parent; this involves single left or right rotations at each step. The expected time for insertion is O(log n).[8]
The following pseudocode illustrates the recursive insertion process (assuming a max-heap on priorities, with larger values indicating higher priority):
Node* insert(Node* [root](/page/Root), int [key](/page/Key), int priority) {
if ([root](/page/Root) == null) {
return new Node([key](/page/Key), priority);
}
if ([key](/page/Key) < [root](/page/Root)->[key](/page/Key)) {
[root](/page/Root)->left = insert([root](/page/Root)->left, [key](/page/Key), priority);
if ([root](/page/Root)->left->priority > [root](/page/Root)->priority) {
[root](/page/Root) = rightRotate([root](/page/Root));
}
} else if ([key](/page/Key) > [root](/page/Root)->[key](/page/Key)) {
[root](/page/Root)->right = insert([root](/page/Root)->right, [key](/page/Key), priority);
if ([root](/page/Root)->right->priority > [root](/page/Root)->priority) {
[root](/page/Root) = leftRotate([root](/page/Root));
}
}
return [root](/page/Root);
}
Node* insert(Node* [root](/page/Root), int [key](/page/Key), int priority) {
if ([root](/page/Root) == null) {
return new Node([key](/page/Key), priority);
}
if ([key](/page/Key) < [root](/page/Root)->[key](/page/Key)) {
[root](/page/Root)->left = insert([root](/page/Root)->left, [key](/page/Key), priority);
if ([root](/page/Root)->left->priority > [root](/page/Root)->priority) {
[root](/page/Root) = rightRotate([root](/page/Root));
}
} else if ([key](/page/Key) > [root](/page/Root)->[key](/page/Key)) {
[root](/page/Root)->right = insert([root](/page/Root)->right, [key](/page/Key), priority);
if ([root](/page/Root)->right->priority > [root](/page/Root)->priority) {
[root](/page/Root) = leftRotate([root](/page/Root));
}
}
return [root](/page/Root);
}
Here, rightRotate and leftRotate are defined below. These rotations ensure the binary search tree property is preserved (in-order traversal yields sorted keys) while enforcing the heap property.[8]
Rotations are local restructuring operations that maintain both tree properties. A right rotation on a node y (with left child x) promotes x to y's position: x's right subtree becomes y's left subtree, and y becomes x's right child. In pseudocode:
Node* rightRotate(Node* y) {
Node* x = y->left;
Node* T2 = x->right;
x->right = y;
y->left = T2;
return x;
}
Node* rightRotate(Node* y) {
Node* x = y->left;
Node* T2 = x->right;
x->right = y;
y->left = T2;
return x;
}
Symmetrically, a left rotation on a node x (with right child y) promotes y: y's left subtree becomes x's right subtree, and x becomes y's left child. In pseudocode:
Node* leftRotate(Node* x) {
Node* y = x->right;
Node* T2 = y->left;
y->left = x;
x->right = T2;
return y;
}
Node* leftRotate(Node* x) {
Node* y = x->right;
Node* T2 = y->left;
y->left = x;
x->right = T2;
return y;
}
Each rotation executes in O(1) time and adjusts parent pointers accordingly if maintaining explicit parent links.[8]
Deletion locates the target node via standard binary search tree traversal. If the node has no children, it is simply removed. If it has one child, that child replaces it. For two children, the node is rotated downward by repeatedly rotating it with the child of higher priority (promoting the higher-priority child) until it becomes a leaf, at which point it is removed. This process restores the heap property and requires an expected O(log n) time.[8]
The following pseudocode shows the recursive deletion (simplified for the two-child case via rotations):
[Node](/page/Node)* deleteNode([Node](/page/Node)* [root](/page/Root), int [key](/page/Key)) {
if ([root](/page/Root) == [null](/page/Null)) return [root](/page/Root);
if ([key](/page/Key) < [root](/page/Root)->[key](/page/Key)) {
[root](/page/Root)->left = deleteNode([root](/page/Root)->left, [key](/page/Key));
} [else](/page/The_Else) if ([key](/page/Key) > [root](/page/Root)->[key](/page/Key)) {
[root](/page/Root)->right = deleteNode([root](/page/Root)->right, [key](/page/Key));
} [else](/page/The_Else) {
if ([root](/page/Root)->left == [null](/page/Null)) {
[Node](/page/Node)* [temp](/page/Temp) = [root](/page/Root)->right;
free([root](/page/Root));
return [temp](/page/Temp);
} [else](/page/The_Else) if ([root](/page/Root)->right == [null](/page/Null)) {
[Node](/page/Node)* [temp](/page/Temp) = [root](/page/Root)->left;
free([root](/page/Root));
return [temp](/page/Temp);
}
// Two children: rotate down
if ([root](/page/Root)->left->priority > [root](/page/Root)->right->priority) {
[root](/page/Root) = rightRotate([root](/page/Root));
[root](/page/Root)->right = deleteNode([root](/page/Root)->right, [key](/page/Key));
} [else](/page/The_Else) {
[root](/page/Root) = leftRotate([root](/page/Root));
[root](/page/Root)->left = deleteNode([root](/page/Root)->left, [key](/page/Key));
}
}
return [root](/page/Root);
}
[Node](/page/Node)* deleteNode([Node](/page/Node)* [root](/page/Root), int [key](/page/Key)) {
if ([root](/page/Root) == [null](/page/Null)) return [root](/page/Root);
if ([key](/page/Key) < [root](/page/Root)->[key](/page/Key)) {
[root](/page/Root)->left = deleteNode([root](/page/Root)->left, [key](/page/Key));
} [else](/page/The_Else) if ([key](/page/Key) > [root](/page/Root)->[key](/page/Key)) {
[root](/page/Root)->right = deleteNode([root](/page/Root)->right, [key](/page/Key));
} [else](/page/The_Else) {
if ([root](/page/Root)->left == [null](/page/Null)) {
[Node](/page/Node)* [temp](/page/Temp) = [root](/page/Root)->right;
free([root](/page/Root));
return [temp](/page/Temp);
} [else](/page/The_Else) if ([root](/page/Root)->right == [null](/page/Null)) {
[Node](/page/Node)* [temp](/page/Temp) = [root](/page/Root)->left;
free([root](/page/Root));
return [temp](/page/Temp);
}
// Two children: rotate down
if ([root](/page/Root)->left->priority > [root](/page/Root)->right->priority) {
[root](/page/Root) = rightRotate([root](/page/Root));
[root](/page/Root)->right = deleteNode([root](/page/Root)->right, [key](/page/Key));
} [else](/page/The_Else) {
[root](/page/Root) = leftRotate([root](/page/Root));
[root](/page/Root)->left = deleteNode([root](/page/Root)->left, [key](/page/Key));
}
}
return [root](/page/Root);
}
This approach handles the structural changes while preserving the required properties.[8]
Construction and Bulk Operations
Treaps can be constructed in two primary ways. The incremental approach involves performing a sequence of insertions on an initially empty treap, each taking expected O(log n) time, leading to an overall expected O(n log n) time for n elements.[8] Alternatively, for a presorted array of keys with assigned random priorities, a treap can be built in O(n) time by constructing the corresponding Cartesian tree, which satisfies both the binary search tree and heap properties directly.[14] This linear-time method leverages the sorted order to efficiently determine parent-child relationships based on priorities via a stack-based algorithm that simulates the recursive structure.[14]
The split operation divides a treap T into two treaps L and R such that all keys in L are less than or equal to a given key k, and all keys in R are greater than k, while preserving the heap order on priorities. This is achieved through recursive descent starting from the root: if the root's key is ≤ k, the left treap L includes the root and its left subtree, and split is recursively called on the right subtree to further partition; otherwise, R includes the root and its right subtree, with recursion on the left. The expected time complexity is O(log n) for a treap of size n.[8][14]
Pseudocode for split (in a style similar to common implementations) is as follows:
void split(Node* t, int [key](/page/Key), Node*& [l](/page/L'), Node*& [r](/page/R)) {
if (!t) {
[l](/page/L') = [r](/page/R) = nullptr;
return;
}
if (t->[key](/page/Key) <= [key](/page/Key)) {
split(t->right, [key](/page/Key), t->right, [r](/page/R));
[l](/page/L') = t;
} [else](/page/The_Else) {
split(t->left, [key](/page/Key), [l](/page/L'), t->left);
[r](/page/R) = t;
}
}
void split(Node* t, int [key](/page/Key), Node*& [l](/page/L'), Node*& [r](/page/R)) {
if (!t) {
[l](/page/L') = [r](/page/R) = nullptr;
return;
}
if (t->[key](/page/Key) <= [key](/page/Key)) {
split(t->right, [key](/page/Key), t->right, [r](/page/R));
[l](/page/L') = t;
} [else](/page/The_Else) {
split(t->left, [key](/page/Key), [l](/page/L'), t->left);
[r](/page/R) = t;
}
}
[14]
The join operation merges two treaps L and R, where all keys in L are strictly less than all keys in R, into a single treap that maintains both properties. This is done recursively by comparing the priorities of the roots of L and R: if the root of L has higher priority, it becomes the root with R joined to its right subtree; otherwise, the root of R becomes the root with L joined to its left subtree. The expected time complexity is O(log n + log m) for treaps of sizes n and m.[8][14]
Pseudocode for join is:
Node* join(Node* l, Node* r) {
if (!l) return r;
if (!r) return l;
if (l->prior > r->prior) {
l->right = join(l->right, r);
return l;
} else {
r->left = join(l, r->left);
return r;
}
}
Node* join(Node* l, Node* r) {
if (!l) return r;
if (!r) return l;
if (l->prior > r->prior) {
l->right = join(l->right, r);
return l;
} else {
r->left = join(l, r->left);
return r;
}
}
[14]
Bulk operations on treaps, such as union, intersection, and difference, are efficiently implemented using repeated applications of split and join, treating treaps as representations of ordered sets. For union of two treaps T1 and T2 (sizes n and m, m ≤ n), recursively split the lower-priority root's treap by the higher-priority root's key, then join the appropriate subtrees, yielding expected O(m log(n/m)) time.[14] Intersection finds common elements by splitting to isolate matching subtrees and joining the results where keys overlap, also in expected O(m log(n/m)) time.[14] Difference computes T1 minus T2 by splitting T2 to discard overlapping parts from T1 and joining the remnants, again in expected O(m log(n/m)) time.[14] These operations output a treap whose size reflects the result cardinality, making them output-sensitive.
Variants
Randomized Binary Search Trees
Randomized binary search trees (RBSTs) are a variant of binary search trees that maintain balance through randomization without assigning explicit priorities to nodes, as in standard treaps. Instead, each node stores the size of its subtree, which enables probabilistic decisions during operations to simulate random ordering. When inserting a new key, the algorithm selects a random rank for it based on the current tree size, effectively mimicking the effect of a random priority by positioning the new node at a uniformly random position in the in-order traversal of the tree. This approach ensures that the resulting structure is a random binary search tree, where every possible BST shape for the given keys is equally likely.[15]
The equivalence between RBSTs and treaps arises because assigning a random rank during insertion in an RBST corresponds directly to assigning a random priority in a treap, both leading to an expected tree height of O(\log n) and amortized O(\log n) time for search, insert, and delete operations, independent of the input order. In RBSTs, the core operations leverage subtree sizes for randomization: the split operation by rank (or position k) traverses the tree using size fields to locate the k-th smallest element, dividing the tree into two subtrees where the left contains the first k elements and the right the remainder, with random rotations preserving the BST property and uniformity. The join operation, which merges two RBSTs where all keys in the left are less than those in the right, selects the root by comparing the subtree sizes of the two candidate roots (with probability proportional to their sizes, e.g., m/(m+n) for the left root where m and n are the sizes), then recursively joins the appropriate subtrees. These mechanisms ensure the joined tree remains a random BST.[15]
A key advantage of RBSTs over standard treaps is the avoidance of floating-point arithmetic for priorities, as ranks are integers derived from subtree sizes, simplifying implementation and reducing precision issues. Additionally, the stored sizes facilitate efficient order statistics operations, such as finding the k-th smallest element or selecting by rank, all in expected O(\log n) time without extra storage. RBSTs were introduced by Martínez and Roura in 1997 as an alternative to priority-based structures, providing the same probabilistic guarantees while enhancing support for positional queries.[15]
Implicit Treaps
Implicit treaps are a variant of treaps adapted for representing dynamic sequences or arrays, where the keys are implicit and derived from the sizes of left subtrees rather than being explicitly stored. This allows the structure to function as a balanced binary tree over a sequence of elements, with the position of each node determined by the number of nodes in its left subtree plus contributions from ancestor paths during traversal. Each node includes the element value, a randomly assigned priority for heap ordering, and a size field tracking the total number of nodes in its subtree, which is updated during rotations to facilitate implicit positioning. The resulting structure maintains the Cartesian tree properties of standard treaps, ensuring expected O(log n) height with high probability due to the random priorities.[1][16]
When adding nodes to an implicit treap, a new node is created with the given value and a randomly chosen priority, typically from a uniform distribution to preserve balance. The insertion occurs at a specified position (index) in the sequence; the tree is first split at that position using the size fields to guide the traversal, separating the prefix and suffix. The new node is then merged between these parts, with rotations applied as needed to restore the max-heap priority order, all in expected O(log n) time. This process leverages the subtree sizes to implicitly compute the insertion point without storing explicit indices.[1]
The core operations of implicit treaps revolve around split and merge, enabling efficient sequence manipulations. The split operation divides the treap into two independent treaps: the left containing the first k elements (by position) and the right containing the rest, performed by descending the tree while subtracting left subtree sizes from k until the split point is reached, with expected O(log n) time complexity. The merge operation concatenates two treaps where all positions in the left precede those in the right, recursively attaching the lower-priority root's subtree to the higher-priority one while preserving order, also in expected O(log n) time; this mirrors the join in standard treaps but uses implicit positioning. Together, these allow array-like operations such as inserting or deleting at any position by splitting, modifying, and merging, all achieving O(log n) expected time.[1]
Implicit treaps uniquely support range-based modifications and queries through lazy propagation mechanisms. Reversals over a range are handled by splitting the range into a subtree, setting a lazy reverse flag on its root to indicate swapping of left and right children (propagated downward only when accessing subtrees), and merging back, enabling O(log n) reversals without immediate restructuring. Range additions propagate a lazy additive value to a subtree root, which is pushed down to affect sums or other aggregates lazily. For range queries like sum, minimum, or maximum, the treap is split to isolate the range, the query is computed on the resulting treap (often augmented with aggregate fields at nodes), and the parts are merged, supporting O(log n) time for these operations when combined with lazy updates.[1]
In essence, an implicit treap is equivalent to a standard treap applied to a sequence where the keys are the consecutive indices (1 to n), with the implicit positioning and size-based navigation providing the index functionality while random priorities ensure probabilistic balance.[1]
Applications and Implementations
Practical Applications
Treaps serve as an effective foundation for order statistic trees by augmenting nodes with subtree sizes, enabling efficient selection of the k-th smallest element in an ordered set through split and join operations, with expected O(log n) time complexity.[8] This capability supports applications requiring dynamic order queries, such as selecting medians or rank-based retrievals in probabilistic data structures.
In computational geometry, treaps facilitate the implementation of range trees, where their randomized balancing minimizes costly rotations during updates, ensuring expected logarithmic access times for point location and nearest-neighbor queries.[8] Their structure proves particularly valuable in parallel algorithms for geometric searching, as the expected height remains O(log n) under random priorities, reducing synchronization overhead.
For database indexing, treaps enable fast set operations like union, intersection, and difference on ordered datasets, which are essential for query processing such as term conjunctions in search engines or relational joins, achieving expected O(m log(n/m)) work for sets of sizes m and n (m ≤ n).[14] These operations support dynamic indexing of large, evolving collections with probabilistic balance, outperforming deterministic trees in scenarios with unpredictable insertion orders.
Treaps have been applied in public-key cryptosystems for maintaining authorization certificates through authenticated data structures, where random treaps represent revocation lists as binary search trees with heap-ordered priorities, allowing efficient membership verification and updates in expected logarithmic time.[17] This approach simplifies implementation compared to balanced trees like 2-3 trees while providing compact proofs of certificate validity or revocation.
Algorithmically, treaps implement meldable priority queues by treating priorities as heap keys and search keys for ordering, supporting insert, delete-min, and merge operations in expected O(log n) time, which is useful for scheduling tasks or Dijkstra's algorithm variants requiring dynamic priority adjustments.[9]
More recent applications include extensions to learning-augmented data structures, such as augmented treaps that incorporate machine learning predictions for faster access times in dictionaries (as of 2022).[18] Treaps are also utilized in memory allocation algorithms and wireless networking protocols for efficient dynamic data management (as of 2021).[19]
Example Implementations
A typical implementation of a treap begins with a node class that encapsulates the essential fields required to maintain both the binary search tree (BST) and heap properties. In a Python-like pseudocode, the node might be defined as follows:
python
class TreapNode:
def __init__(self, key, value=None):
self.key = key
self.value = value
self.priority = random.randint(0, 2**31 - 1) # Random priority for [heap](/page/Heap) property
self.left = None
self.right = None
self.size = 1 # Optional: subtree size for additional operations
class TreapNode:
def __init__(self, key, value=None):
self.key = key
self.value = value
self.priority = random.randint(0, 2**31 - 1) # Random priority for [heap](/page/Heap) property
self.left = None
self.right = None
self.size = 1 # Optional: subtree size for additional operations
This structure includes the key for BST ordering, an optional value, a randomly assigned priority to enforce the max-heap property (higher priority closer to root), and pointers to left and right children; the size field can facilitate order statistics if needed.[3]
Random priorities are generated uniformly from a large range, such as 0 to 2^31 - 1, to ensure a low probability of ties and promote balanced trees in expectation; this range is sufficiently large relative to typical dataset sizes to minimize duplicate priorities.[3]
The basic insert operation proceeds recursively, first inserting the new node as in a standard BST, then performing rotations to restore the heap property based on priorities, as rotations preserve the BST property. The following pseudocode illustrates this in a Python-like style:
python
import random
def rotate_right(root):
new_root = root.left
root.left = new_root.right
new_root.right = root
return new_root
def rotate_left(root):
new_root = root.right
root.right = new_root.left
new_root.left = root
return new_root
def insert(root, key, value=None):
if root is None:
return TreapNode(key, value)
if key < root.key:
root.left = insert(root.left, key, value)
if root.left.priority > root.priority:
root = rotate_right(root)
elif key > root.key:
root.right = insert(root.right, key, value)
if root.right.priority > root.priority:
root = rotate_left(root)
else:
# Handle duplicate key, e.g., update value or raise error
root.value = value
return root
return root
import random
def rotate_right(root):
new_root = root.left
root.left = new_root.right
new_root.right = root
return new_root
def rotate_left(root):
new_root = root.right
root.right = new_root.left
new_root.left = root
return new_root
def insert(root, key, value=None):
if root is None:
return TreapNode(key, value)
if key < root.key:
root.left = insert(root.left, key, value)
if root.left.priority > root.priority:
root = rotate_right(root)
elif key > root.key:
root.right = insert(root.right, key, value)
if root.right.priority > root.priority:
root = rotate_left(root)
else:
# Handle duplicate key, e.g., update value or raise error
root.value = value
return root
return root
This insert function assumes a max-heap on priorities (higher values at root) and handles duplicates by updating the value; in practice, the root of the treap is passed by reference or reassigned after the call.[8]
Implementation considerations include memory management, where languages like C++ use explicit pointers and manual deallocation to avoid leaks, contrasting with garbage-collected languages like Java or Python that handle object lifecycles automatically but may incur overhead from frequent allocations. Thread-safety requires external locking (e.g., mutexes around insert/delete) since concurrent modifications can violate properties without synchronization; optimizations in C++ might leverage inline rotations for speed, while Java benefits from object pooling to reduce instantiation costs.[3]
For the split operation, which divides a treap into two treaps—one with keys less than or equal to a given key k and the other with keys greater than k—the following recursive pseudocode returns the pair of resulting treaps:
def split([root](/page/Root), [key](/page/Key)):
if [root](/page/Root) is None:
return None, None
if [root](/page/Root).[key](/page/Key) <= [key](/page/Key):
left, right = split([root](/page/Root).right, [key](/page/Key))
[root](/page/Root).right = left
return [root](/page/Root), right
else:
left, right = split([root](/page/Root).left, [key](/page/Key))
[root](/page/Root).left = right
return left, [root](/page/Root)
def split([root](/page/Root), [key](/page/Key)):
if [root](/page/Root) is None:
return None, None
if [root](/page/Root).[key](/page/Key) <= [key](/page/Key):
left, right = split([root](/page/Root).right, [key](/page/Key))
[root](/page/Root).right = left
return [root](/page/Root), right
else:
left, right = split([root](/page/Root).left, [key](/page/Key))
[root](/page/Root).left = right
return left, [root](/page/Root)
This implementation maintains both BST and heap properties in the output treaps through the recursive partitioning and pointer adjustments, without needing additional rotations.[20]