Why Randomized Data Structures Matters
Suppose a service maintains changing integer scores. It must insert and remove scores, test membership, and enumerate the scores in ascending order. A sorted array makes searching fast but shifts elements on updates. A plain binary search tree avoids those shifts, yet inserting already sorted keys can produce a chain. A linked list also becomes slow to search as it grows.
Treaps and skip lists use random structural choices to provide efficient dynamic ordered sets. A treap gives each key a random priority and restores a heap order with tree rotations. A skip list gives each key a random tower height, creating sparse express lanes above a sorted linked list. Both preserve exact answers for every valid completed state; randomness affects the shape and running time.
You will trace both structures on the same insertions and deletions, implement them with matching interfaces in three languages, and distinguish expected performance from a worst-case guarantee. The programs support insert, erase, contains, and sorted enumeration. Predecessor, range, rank, and sequence operations are natural extensions, discussed after the core implementation.
Randomized ordered sets maintain exact sorted membership while using random priorities or tower heights to keep search paths short in expectation. Treaps repair a binary search tree with rotations; skip lists splice nodes into several sorted levels. Their expected logarithmic performance depends on the random-choice model, while their correctness depends on deterministic ordering and link invariants.
Core Concepts and Terminology
The input is a sequence of operations on distinct integer keys. insert(x) returns true only when a new key is added; erase(x) returns true only when an existing key is removed. contains(x) returns membership without changing the structure or consuming randomness. Duplicate insertions and unsuccessful deletions leave both structures and their generator states unchanged.
A treap combines two orders. Its keys satisfy the binary search tree rule: every key in a node's left subtree is smaller, and every key in its right subtree is larger. Its priorities satisfy a min-heap rule: the parent's priority rank is smaller than each child's. Key order determines where searching goes; priority order determines which key rises above its neighbors. Smaller priority values are better in this lesson.
The programs compare priority ranks lexicographically as $(p,s)$, where $p$ is the generated value and $s$ is an increasing insertion serial number. A tie in $p$ is resolved by $s$. Distinct ranks make the treap for a fixed collection of key–rank pairs unique. This structural fact does not make a finite generator equivalent to independent continuous priorities.
A skip list has level $0$, containing every key once, and higher levels containing increasingly sparse subsequences. A node of height $h$ owns forward pointers at levels $0$ through $h-1$. Each pointer leads to the next larger key participating at that level, or to an empty link. A separate head node starts every level; its stored key is never compared, so zero and negative keys remain ordinary data.
With independent fair promotion choices, a tower begins at height $1$, rises after each promotion, and stops at the first nonpromotion:
$$\Pr(h=k)=2^{-k},\qquad \Pr(h\ge k)=2^{-(k-1)},\qquad k\ge1.$$
The diagram separates these random choices from the key-order rules that remain mandatory.
| Term | Meaning, example, and purpose |
|---|---|
| Ordered set | A changing collection of distinct keys; enumerating $\{10,20,30\}$ returns them in that order. |
| Treap priority rank | A pair $(p,s)$ that determines heap order; keys still determine left and right branches. |
| Rotation | A local parent–child exchange that preserves the complete in-order key sequence. |
| Treap height | Number of nodes on the longest root-to-leaf path; an empty tree has height $0$. |
| Tower height | Number of forward links stored by one skip-list node; height $3$ means levels $0,1,2$. |
| Promotion | An additional level granted to a new node; ideal probability $1/2$ per independent trial. |
| Predecessor array | update[level] records the last node with key smaller than the target at that level. |
| Head node | A structural entry point with no data-key ordering role; it retains one empty level when the set is empty. |
| Expected bound | An average over specified random choices for a fixed operation sequence, rather than a promise for every seed. |
| Randomness replay | Matching seeds, generator arithmetic, draw order, and duplicate policy reproduce the same structures. |
The abstract comparison model permits any consistently ordered keys. To obtain identical typed programs here, use signed $32$-bit integer keys, valid seeds from $1$ through $2147483646$, and operation counts that fit the native counters. Python can represent wider integers, but cross-language examples use the shared domain.
How Randomized Data Structures Works, Step by Step
Treap insertion. Search by key while recording the ancestor path. If the key exists, return false before drawing a priority. Otherwise draw one priority, assign a fresh serial number, and attach the new node as a binary-search-tree leaf. While its rank is smaller than its parent's rank, rotate it above that parent and reconnect the resulting subtree to the grandparent or the root. Stop when heap order is restored.
For a right child promoted by a left rotation, save its left subtree as the old parent's new right subtree. The mirrored rule applies to a left child promoted by a right rotation. These middle subtrees are real data, even when the small insertion example happens to leave them empty.
Treap deletion. Search for the key. If absent, return false. Otherwise rotate the target downward, always promoting its smaller-ranked child when it has two children. Once the target becomes a leaf, detach it. The target may temporarily violate heap order during this internal procedure; after it is removed, all remaining nodes satisfy both orders. No new priorities are drawn.
Skip-list search. Start at the head's highest active level. Move right while the next key is strictly smaller than the target, then drop one level without returning to the head. At level $0$, compare the next key with the target. Stopping before equality also gives the predecessor positions needed for updates.
Skip-list insertion. Record those predecessors and reject an existing key before drawing a height. Start with height $1$ and promote while the next generator output is even. If necessary, extend the head and set the new predecessor entries to the head. At each level in the new tower, set its successor link first, then redirect the predecessor to the new node.
Skip-list deletion. Find the same predecessor array. Redirect every incoming link to the target's successor at that level, then discard the node. Remove empty top head levels while keeping level $0$. Updating only the base list leaves stale upper links.
The following diagram isolates insertion of $25$ into the sample's local treap subtree and its two skip-list levels. The priority ordering is $p(30)\lt p(25)\lt p(20)$.
flowchart TD
A["Insert key into an ordered set"] --> B{"Treap or skip list?"}
B -->|Treap| C["Search by key; save ancestors"]
B -->|Skip list| D["Search top down; save predecessors"]
C --> E{"Key already present?"}
D --> E
E -->|Yes| F["Return false; draw nothing"]
E -->|No| G{"Chosen structure?"}
G -->|Treap| H["Draw priority; attach BST leaf"]
H --> I{"New node outranks parent?"}
I -->|Yes| J["Rotate up; reconnect subtree"]
J --> I
I -->|No| K["Increment size; return true"]
G -->|Skip list| L["Start tower height at one"]
L --> M{"Next draw is even?"}
M -->|Yes| N["Increase tower height"]
N --> M
M -->|No| O["Extend head; splice every tower level"]
O --> K
All traversals and updates in the programs are iterative. This avoids call-stack failure on an unusually deep treap; it does not change the time needed to traverse that treap. The explicit insertion path still uses memory proportional to its length.
Worked Example with a Full Trace
Insert $[40,20,60,10,30,50,70,25]$ into both initially empty structures. Use treap seed $7$ and skip-list seed $11$. Each structure owns its own generator, with state transition
$$s_{t+1}=16807s_t\bmod2147483647.$$
The treap consumes one raw output for each successful insertion. The skip list consumes one output per promotion test, including the final odd output that stops the tower. In the table, E means an even output and O an odd output.
| Insert | Treap priority | Rotations during insertion | Skip raw draw indices | Promotion outcomes | New tower height |
|---|---|---|---|---|---|
| $40$ | $117649$ | None | $1$ | O | $1$ |
| $20$ | $1977326743$ | None | $2,3$ | E, O | $2$ |
| $60$ | $621132276$ | None | $4$ | O | $1$ |
| $10$ | $452154665$ | Promote $10$ above $20$ | $5$ | O | $1$ |
| $30$ | $1566311569$ | Promote $30$ above $20$ | $6,7,8$ | E, E, O | $3$ |
| $50$ | $1143995257$ | None | $9,10$ | E, O | $2$ |
| $70$ | $707192808$ | None | $11,12,13$ | E, E, O | $3$ |
| $25$ | $1615021558$ | Promote $25$ above $20$ | $14,15$ | E, O | $2$ |
For a numerical replay of every promotion test, the skip generator outputs are $[184877,959744092,669281627,96962003,1847779995,877356698,1111302984,1003974129,1014171624,604778329,469274252,1532401580,301976589,816673462,1262887857]$. These values give tower heights $[1,2,1,1,3,2,3,2]$ in insertion order, totaling $15$ node forward pointers.
The eight panels show the complete treap after each insertion. Orange marks the newly inserted key. The bottom lanes show the final skip list; omitted empty successors terminate each lane.
The final treap has root $40$, height $5$, and three insertion rotations. Its edges, written as parent: left,right with - for an empty child, are 40:10,60, 10:-,30, 30:25,-, 25:20,-, and 60:50,70; the other nodes are leaves. These are tree relationships, not an insertion-order list.
Now try inserting $25$ again. Both structures return false, consume no draws, and retain their layouts. Erase $40$, then $10$, then $25$, and finally absent key $999$:
| Operation | Treap promotions while removing the target | Treap root after operation | Cumulative rotations | Skip levels / live node pointers | Result |
|---|---|---|---|---|---|
| Erase $40$ | $10,60,50,30$ | $10$ | $7$ | $3$ / $14$ | True |
| Erase $10$ | $60,50,30,25,20$ | $60$ | $12$ | $3$ / $13$ | True |
| Erase $25$ | $20$ | $60$ | $13$ | $3$ / $11$ | True |
| Erase $999$ | None | $60$ | $13$ | $3$ / $11$ | False |
Both final key sequences are $[20,30,50,60,70]$. The treap has edges 60:50,70, 50:30,-, and 30:20,-, with height $4$. The skip-list lanes are level $2$: $[30,70]$; level $1$: $[20,30,50,70]$; and level $0$: $[20,30,50,60,70]$. The draw counts stay at $8$ and $15$ throughout deletion.
Trace It by Hand
After the first seven insertions, predict where key $25$ is attached in the treap and which ancestor it overtakes. It first becomes the right child of $20$; its priority $1615021558$ is smaller than $1977326743$, so a left rotation promotes it above $20$, but it remains below $30$ because $1566311569$ is smaller still. In the skip list, the next outputs are $816673462$ and $1262887857$: one promotion then a stop gives height $2$, so splice $25$ between $20$ and $30$ at levels $0$ and $1$ only.
Implementation in Python, C++, and Java
Each panel is a complete runnable program implementing both structures. The treap uses a total priority rank and an explicit ancestor path. The skip list grows its head dynamically and imposes no fixed tower cap. keys() returns sorted keys; height() measures treap height; pointers() counts live data-node forward slots, including slots whose successor is empty and excluding head slots. These diagnostic scans take linear time in the number of nodes and are not part of a normal membership query.
All three programs use the same Park–Miller recurrence, with signed $64$-bit intermediate arithmetic in C++ and Java. Since $16807(P-1)\lt2^{63}$, the state update is safe. The valid nonzero seeds remain in the generator's nonzero state domain. Separate generators ensure that changes in one implementation's random consumption do not alter the other's trace.
The expected-time analysis below assumes independent uniform priority orderings and independent fair promotion choices. This small deterministic generator is used for reproducibility; its consecutive outputs are correlated, and a fixed seed is not a proof of those assumptions. Priority tie-breaking preserves correctness, and an unfavorable sequence can still create a tall structure. The programs are sequential teaching implementations; concurrent access requires a separate synchronization design.
Run Python 3 directly, compile C++ with g++ -std=c++14 -O2 main.cpp -o randomized, or compile Java 17 or later with javac Main.java and run java Main. Each program prints these fourteen lines, with treap and skip-list boolean results in that order:
after inserts = [10,20,25,30,40,50,60,70]treap height=5, rotations=3skip levels=3, node pointers=15duplicate insert = 0,0erase 40 = 1,1erase 10 = 1,1erase 25 = 1,1erase 999 = 0,0remaining = [20,30,50,60,70]contains 25=0,0; 60=1,1treap height=4, rotations=13skip levels=3, node pointers=11raw draws treap=8, skip=15checks = PASS
Complexity, Correctness, and Invariants
Let $n$ be the current number of keys. For the skip list, let $L$ be the greatest active level count before or after the operation, including a newly sampled tower, and let $h_i$ be each live node's tower height. Bounds below use constant-cost comparisons, pointer accesses, and random draws. Enumeration returns $n$ keys in $\Theta(n)$ time in either structure.
For a treap with distinct independent continuous priorities, the minimum-priority key in any fixed interval is uniformly distributed within that interval. If a target has zero-based sorted rank $r$, a smaller key at rank $j$ is its ancestor exactly when it has minimum priority among ranks $j$ through $r$. Its probability is $1/(r-j+1)$. Summing the left and right ancestor probabilities yields
$$\mathbb E[\operatorname{depth}(r)]=H_{r+1}+H_{n-r}-2=O(\log(n+1)),$$
where $H_k=\sum_{i=1}^k1/i$. This explains expected search cost without assuming that input keys arrive randomly. Treap insertion rotates only along its search path. Deletion combines the left and right subtrees by repeatedly promoting the smaller-ranked child; the corresponding randomized merge path also has expected logarithmic length. Thus search, insertion, and deletion have expected $O(\log(n+1))$ time for a fixed operation sequence under the ideal priority model. A chain remains possible, so their deterministic worst case is $O(n+1)$.
For independent fair skip-list promotions, the tail-sum formula gives
$$\mathbb E[h_i]=\sum_{k\ge1}2^{-(k-1)}=2,\qquad \mathbb E\!\left[\sum_{i=1}^n h_i\right]=2n.$$
The maximum tower height obeys $\Pr(L\ge k)\le\min(1,n2^{-(k-1)})$ for a fixed nonempty set of independently generated live towers. Summing that tail gives expected $O(\log(n+1))$ active levels. In the usual backward search-path analysis, only a constant expected number of horizontal steps is needed per level before reaching a node promoted to the next level. Combining those facts gives expected logarithmic search and update time. Generating and linking one new tower costs an additional expected constant amount.
| Structure | Search / insert / erase | Space and assumptions |
|---|---|---|
| Treap | Best case $O(1)$; expected $O(\log(n+1))$; worst case $O(n+1)$ | $\Theta(n+1)$ nodes; insertion path uses up to $O(n)$ extra entries. Expectation needs random priority order independent of the fixed key sequence. |
| Uncapped skip list here | Best case over states $O(1)$; expected $O(\log(n+1))$; layout-sensitive upper bound $O(n+L)$ | $\Theta(1+L+\sum_i h_i)$ logically live links; expected $O(n+1)$ space under independent promotions. There is no finite worst-case bound in terms of $n$ alone for ideal uncapped heights. |
| AVL or red–black tree | Worst-case $O(\log(n+1))$ search and updates | Linear space; deterministic balance maintenance and additional balancing metadata. |
| Unbalanced binary search tree | Best $O(1)$, worst $O(n+1)$ | Linear space; sorted insertion can create a chain. No random balancing mechanism. |
| Sorted dynamic array | Search $O(\log(n+1))$; updates $O(n+1)$ in the worst case | Linear storage; compact traversal, with shifts on insertion or deletion. |
| Ordinary hash set | Usually expected amortized constant updates and expected constant membership under hashing assumptions | Linear space at controlled load; sorted enumeration and ordered navigation need additional work. |
For a fixed skip-list state, even a very favorable search visits every active level, so its best search cost is $\Theta(L)$ in these programs. A layout with all towers of height $1$ gives linear searching. Conversely, even one ideal tower can be arbitrarily tall: saying merely “worst-case $O(n)$” would omit the uncapped vertical work. A chosen finite height cap changes that worst-case statement and must be included in the analysis.
Treap searches and deletions use constant auxiliary space; insertion uses its explicit path. Skip-list updates use an $O(L)$ predecessor array, while membership uses constant auxiliary space. Diagnostic output allocates the returned list of keys. The link-space bound counts logically live entries; dynamic containers and garbage collectors may retain unused allocated capacity after shrinking or deletion.
A completed treap satisfies both strict BST key order and strict min-heap order on its total priority ranks; rotations preserve key order while updates restore heap order. A completed skip list has every key exactly once at level $0$, with every higher level equal to the sorted subsequence of nodes tall enough to participate. Those invariants make membership and updates exact regardless of how favorable the random choices were.
To justify a rotation, consider keys in the transferred middle subtree: they lie between the old parent and the promoted child, so they remain in the correct interval after the exchange. The grandparent's link or the root must then point to the promoted child. During deletion, choosing the smaller-ranked child is exactly the root choice for merging the two remaining treaps; repeating it preserves the final heap order once the target disappears.
For skip-list search, the cursor remains the head or a node with key smaller than the target. Moving right never overshoots the target, and dropping a level preserves this fact because upper-level nodes participate at all lower levels. At level $0$, the successor is therefore the first possible matching key. Splicing at every recorded predecessor maintains each level's sorted subsequence.
Correctness is deterministic; expected balance is probabilistic. The standard guarantees apply to fixed, or otherwise suitably randomness-independent, operation sequences. An adversary that observes the shape and selectively removes tall towers or chooses keys from exposed priorities can invalidate the usual distributional argument. Likewise, matching one sample's statistics verifies a replay, not a general running-time theorem.
Variants, Pitfalls, and Optimizations
Deletion exposes two particularly useful mistakes. A skip-list node removed only from level $0$ still has incoming upper links; freeing it in C++ leaves dangling pointers. A treap deletion that promotes the larger-ranked child can leave a heap violation among the survivors. The diagram uses a separate three-key treap with priority ranks $1,2,3$ to isolate that second error.
Practical extensions build on the same invariants:
- Ordered navigation and ranges: search for the first key at least a target, then walk successors. A skip list already has a base-level successor chain. A treap can enumerate a range with an explicit traversal stack; expected setup is logarithmic and reporting $k$ keys costs an additional $O(k)$.
- Order statistics: maintain treap subtree sizes for rank and selection. Update sizes after every rotation and structural change. A skip list needs spans measuring how many base-level nodes each link crosses; tower height alone does not encode rank.
- Split and merge: split a treap by key, then merge treaps whose key ranges do not overlap. These operations lead to implicit treaps, where subtree position replaces an explicit search key, and to sequence editing with carefully maintained lazy metadata.
- Multisets and maps: attach a multiplicity or value instead of creating indistinguishable duplicate-key nodes. Specify whether replacing a value consumes randomness; the set interface here rejects duplicates without drawing.
- Promotion tuning: with independent promotion probability $p$, the expected node height is $1/(1-p)$. Smaller $p$ uses fewer links but increases horizontal search work. Changing $p$ changes the distribution and performance analysis.
- Tower caps: a cap $C$ bounds vertical work and gives search an $O(n+C)$ worst-case bound. Under independent half-promotions, a useful expected upper bound includes $O(C+n/2^{C-1})$; a fixed small cap eventually leaves too many nodes in the top lane. Choose a cap against a supported size range or analyze a resizing policy.
- Persistence and concurrency: persistent treaps copy modified paths; concurrent skip lists require atomic link updates and a safe memory-reclamation protocol. The sequential code's pointer assignments do not provide either feature automatically.
Do not confuse a treap with a heap ordered by its data keys: its priorities and search keys serve different purposes. Do not regenerate a resident node's priority during searches, return to the head after every skip-list level drop, or sample a duplicate's unused height. Each changes either correctness, cost, or the reproducible random stream.
Reconnect the grandparent or root after every treap rotation, preserve the transferred middle subtree, and promote the smaller-ranked child during deletion. In a skip list, preserve strict sorted order at every level, unlink a deleted node at every level it occupies, and remove empty top levels. Treat seed replay, finite-priority ties, and a tower cap as explicit design choices rather than evidence of a worst-case logarithmic guarantee.
Practice Problems and Real-World Use
Validate the two implementations against an independent reference set after every operation. Check results, sizes, sorted enumeration, duplicates, absent deletions, negative keys, and the smallest and largest signed integer keys. Separately inspect treap key intervals and parent–child priority ranks, and compare every skip-list lane with the subsequence of base nodes whose heights reach it. Membership tests alone can miss broken upper links and orphaned subtrees.
Use sorted, reverse-sorted, alternating-extreme, and random operation streams. Delete the treap root repeatedly, delete the only tallest tower, empty both structures completely, and reinsert removed keys. Inject tied and monotone treap priorities and deliberately tall towers to exercise structural cases that an ordinary seed may rarely generate. Check identical state and draw counts across languages, not just the final key list.
| Exercise or system | Idea tested | Difficulty |
|---|---|---|
| Dynamic unique-score index | Insert, erase, membership, and sorted snapshots with explicit duplicate semantics. | Beginner |
| Treap rotation laboratory | Preserve key intervals, middle subtrees, and root links in both rotation directions. | Intermediate |
| Skip-list deletion audit | Remove a node at all its levels; test tallest-tower deletion and an empty result. | Intermediate |
| Dynamic rank and selection | Add subtree sizes to a treap or link spans to a skip list, then validate against a sorted array. | Advanced |
| Implicit-treap sequence editor | Split by position, merge, and maintain lazy reversal with aggregate metadata. | Advanced |
| Range-query benchmark | Compare search setup, ordered reporting, node allocation, and cache behavior across structures. | Intermediate |
| Redis sorted sets | Study an ordered score/member index and why a system may combine indexing structures. | Advanced |
| Persistent or concurrent index | Separate structural invariants from version sharing, synchronization, and memory reclamation. | Research-oriented |
For further reading, see the treap chapter in Open Data Structures and William Pugh's A Skip List Cookbook. Redis's sorted-set documentation gives a practical ordered-data use case; the programs above implement integer sets, without Redis's score/member API or production concurrency behavior.