Build each jump out of two shorter ones
Every node except the root has one parent. Write for the parent of node , and let be the node reached by climbing exactly edges from . The first entry is the parent. Each longer entry follows two jumps of half the length.
Use to mean that an ancestor is missing, and propagate it through all longer jumps. A request for zero edges returns the starting node. A request for exactly the node's depth reaches the root, and any larger request returns .
The fifth ancestor of node 7
Take a fictional hierarchy with node IDs 0 through 7 and parent array in that order. Node 0 is the root. A few rows of the jump table show how the query works.
| Starting node | 1 edge | 2 edges | 4 edges |
|---|---|---|---|
| 7 | 6 | 5 | 1 |
| 5 | 3 | 1 | |
| 1 | 0 | ||
| 0 |
-1 would read the last element of the row and could turn a missing ancestor into an ordinary node ID.Query the set bits
The code assumes a valid, nonempty rooted tree with IDs from 0 through , a valid starting node and an integer . The largest possible climb is edges, so the table only needs enough columns to represent that distance. The query checks the bits of from largest to smallest and takes the corresponding jump each time it finds a 1.
def build_jumps(parents: tuple[int, ...]) -> list[list[int]]:
"""Store jumps by power, then by node."""
jumps = [list(parents)]
columns = max(1, (len(parents) - 1).bit_length())
for _ in range(1, columns):
shorter = jumps[-1]
jumps.append([shorter[middle] if middle != -1 else -1 for middle in shorter])
return jumps
def kth_ancestor(jumps: list[list[int]], node: int, k: int) -> int:
"""Climb k parent edges and return -1 if the ancestor is missing."""
bits = k.bit_length()
if bits > len(jumps):
return -1
for power in range(bits - 1, -1, -1):
if (k >> power) & 1:
node = jumps[power][node]
if node == -1:
return -1
return node
jumps = build_jumps((-1, 0, 0, 1, 1, 3, 5, 6))
print(kth_ancestor(jumps, 7, 5))
# 0
print(kth_ancestor(jumps, 7, 6))
# -1
print(kth_ancestor(jumps, 7, 0))
# 7For nodes, this table takes time to build and space to store. It answers each query in time under constant-time array access and word operations. A direct walk can be cheaper for a few queries on a shallow tree. Reuse the table across queries on a fixed hierarchy, and rebuild it if parent links change. If a book can report to several parents, specify which parent relation to follow before asking for a unique ancestor.
Sources
- Michael A. Bender and Martín Farach-Colton analyse power-of-two ancestor pointers and their running time in Section 3.2. The Level Ancestor Problem Simplified
- Antti Laaksonen covers finding an ancestor through powers of two in Section 18.1 and using the same table for common ancestors in Section 18.3. Competitive Programmer’s Handbook