Efficient recursive random sampling
Data Structures & Algorithms practice on Codemia
Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.
Introduction
Recursive random sampling becomes useful when the data is naturally recursive, such as a tree, nested folders, or hierarchical categories. Instead of flattening the whole structure first, you can sample efficiently by storing subtree sizes and descending only into the branch that contains the randomly chosen position.
Sampling from a Recursive Structure
Suppose you have a tree and want to choose one node uniformly at random. A slow solution is:
- traverse the whole tree
- put every node into a list
- call a random choice on that list
That works, but it costs extra memory and repeated full traversals if you sample often.
A better idea is to let each node store the number of nodes in its subtree. Then:
- compute the total size once
- draw a random integer from
0tosize - 1 - recurse into the child subtree whose size range contains that index
This makes each sample proportional to the height of the tree instead of the total number of nodes.
A Concrete Tree Example
Here is a simple Python implementation:
The key idea is that each node owns a contiguous index range:
- index
0means the current node - the next range belongs to the first child subtree
- then the second child subtree, and so on
Choosing a random index uniformly over the total subtree size guarantees a uniform sample over all nodes.
Why This Is Efficient
After the initial compute_size pass, a single sample only walks one path through the tree. If the tree height is small relative to the number of nodes, this is much cheaper than flattening on every call.
This pattern is especially useful when:
- the structure is large
- you need many repeated samples
- the data is already stored recursively
The tradeoff is that subtree sizes must stay correct. If the tree changes, you need to recompute sizes or update them incrementally.
Extending the Idea to k Samples
If you need several samples, the simplest option is to call sample() repeatedly. That samples with replacement, meaning the same node may appear more than once.
If you need sampling without replacement, the problem becomes more complex. One approach is to remove chosen nodes logically and update subtree sizes after each selection. Another approach is to flatten once if k is large relative to the data size.
This is a good example of an algorithmic tradeoff:
- many samples with replacement: recursive indexed sampling is excellent
- many samples without replacement: flattening or a specialized structure may be simpler
Relation to Weighted Sampling
The same recursive idea works for weighted sampling. Instead of storing subtree node counts, store subtree weights.
For example, if each leaf represents a product with a probability weight, you can:
- store the total weight of each subtree
- draw a random number between
0and total weight - descend into the child whose cumulative weight range contains that number
That turns the tree into a recursive weighted-choice structure.
Common Pitfalls
The most common bug is stale subtree sizes. If you add or remove nodes and forget to update sizes, the random index mapping becomes wrong and the sample is no longer uniform.
Another problem is calling recursive sampling on a very deep tree in a language with a shallow recursion limit. In that case, an iterative descent with a loop may be safer.
It is also easy to claim an algorithm is “efficient” while still recomputing sizes before every sample. The real gain appears when you compute metadata once and reuse it for many selections.
Summary
- Recursive random sampling is useful when the data itself is recursive.
- Store subtree sizes so one random index can be mapped to a node efficiently.
- After preprocessing, each sample follows one path instead of flattening the full structure.
- The same technique extends naturally from uniform sampling to weighted sampling.
- Keep subtree metadata up to date or the distribution becomes incorrect.
Related reading
- Efficient set intersection of a collection of sets in C
- Efficient string truncation algorithm, sequentially removing equal prefixes and suffixes
- Efficient way of calculating likeness scores of strings when sample size is large?
- Efficient way of iterating over true bits in stdbitset?
- Efficient substring Search in DynamoDB
- Efficient time and space complexity data structure for dense and sparse matrix
- Efficient way to compare two arrays
- Efficient way to compute geometric mean of many numbers

DSA Fundamentals
Master algorithmic patterns and data structures through hands-on LeetCode-style problems - from arrays and hashing to dynamic programming and advanced graphs.
View the courseTrack what you have practised
A free account saves your progress, solutions and study plan across every problem on Codemia.
Data Structures & Algorithms practice on Codemia
Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.