C++
Order Statistic Tree
Data Structures
Programming
C++ STL

Order Statistic Tree in C

Data Structures & Algorithms practice on Codemia

Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.

Practice algorithms

Introduction

An order statistic tree is a balanced binary search tree augmented with subtree sizes. That extra size field makes it possible to answer "what is the k-th smallest element" and "what is the rank of this key" in logarithmic time, provided the tree stays balanced.

What makes the tree special

A normal binary search tree stores keys and pointer relationships. An order statistic tree stores one more piece of data on every node: the size of the subtree rooted at that node.

That single value enables two important operations:

  • 'select(k): return the k-th smallest element'
  • 'rank(x): return how many elements are smaller than x'

The idea is simple. If the left subtree has size L, then:

  • the current node is the (L + 1)-th smallest element
  • anything smaller must be in the left subtree
  • anything larger must account for the whole left subtree plus the current node

Minimal node structure in C++

Here is a small node definition that demonstrates the augmentation:

cpp
1#include <iostream>
2
3struct Node {
4    int key;
5    int size;
6    Node* left;
7    Node* right;
8
9    explicit Node(int value)
10        : key(value), size(1), left(nullptr), right(nullptr) {}
11};
12
13int subtree_size(Node* node) {
14    return node ? node->size : 0;
15}
16
17void update_size(Node* node) {
18    if (node) {
19        node->size = 1 + subtree_size(node->left) + subtree_size(node->right);
20    }
21}

This is enough to illustrate the data structure even before adding full balancing logic.

Implement select

To find the k-th smallest element, compare k with the size of the left subtree.

cpp
1Node* select_kth(Node* root, int k) {
2    if (!root || k <= 0 || k > subtree_size(root)) {
3        return nullptr;
4    }
5
6    int left_size = subtree_size(root->left);
7
8    if (k == left_size + 1) {
9        return root;
10    }
11    if (k <= left_size) {
12        return select_kth(root->left, k);
13    }
14    return select_kth(root->right, k - left_size - 1);
15}

If the left subtree contains 3 nodes, then the root is the 4th smallest value in the subtree rooted at that node.

Implement rank

The rank of a key is the number of values smaller than it. Again, subtree sizes do most of the work.

cpp
1int rank_of(Node* root, int key) {
2    if (!root) {
3        return 0;
4    }
5
6    if (key < root->key) {
7        return rank_of(root->left, key);
8    }
9    if (key > root->key) {
10        return subtree_size(root->left) + 1 + rank_of(root->right, key);
11    }
12
13    return subtree_size(root->left);
14}

This version returns a zero-based rank. If you want one-based ranking, add 1 to the final answer.

Maintain sizes during insertion

The augmentation is only useful if every insert, delete, and rotation updates the size field correctly.

cpp
1Node* insert(Node* root, int key) {
2    if (!root) {
3        return new Node(key);
4    }
5
6    if (key < root->key) {
7        root->left = insert(root->left, key);
8    } else if (key > root->key) {
9        root->right = insert(root->right, key);
10    }
11
12    update_size(root);
13    return root;
14}

This sample is an ordinary binary search tree, not a balanced one. It demonstrates the size bookkeeping, but it does not guarantee logarithmic height.

Why balancing matters

Order statistic queries are only fast when the underlying tree remains balanced. If inserts arrive in sorted order and the tree degenerates into a linked list, select and rank become O(n).

That is why real order statistic trees are usually built on top of:

  • red-black trees
  • AVL trees
  • policy-based balanced trees provided by some toolchains

When rotations happen, node sizes must be recomputed immediately after pointers change. That detail is where many bugs hide.

Example usage

cpp
1int main() {
2    Node* root = nullptr;
3    int values[] = {20, 10, 30, 5, 15, 25, 35};
4
5    for (int value : values) {
6        root = insert(root, value);
7    }
8
9    Node* third = select_kth(root, 3);
10    if (third) {
11        std::cout << "3rd smallest: " << third->key << "\n";
12    }
13
14    std::cout << "Rank of 25: " << rank_of(root, 25) << "\n";
15}

With the values above, the 3rd smallest element is 15, and the zero-based rank of 25 is 4.

Common Pitfalls

The most common mistake is forgetting to update size after structural changes. Inserts, deletes, and especially rotations all change subtree membership.

Another issue is assuming the augmentation alone guarantees logarithmic performance. It does not. Balance is a separate property that must be preserved by the underlying tree algorithm.

You also need a clear convention for duplicates. If duplicate keys are allowed, define whether they go left, right, or are counted in a multiplicity field. Without that policy, rank and select become inconsistent.

Finally, be explicit about indexing. Some code treats the smallest element as rank 0, while other code treats it as rank 1. Pick one convention and keep it consistent across the API.

Summary

  • An order statistic tree is a balanced search tree with subtree sizes stored on each node.
  • The size field enables efficient select(k) and rank(x) operations.
  • Every structural update must also maintain the size metadata.
  • Balance still matters; without it, the tree loses logarithmic performance.
  • Clear duplicate handling and rank indexing conventions are part of a correct implementation.

Related reading
Course
Intermediate
27 lessons
15 hours
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 course
Track 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.

Practice algorithms

All Rights Reserved.