Interval Trees
An Interval Tree is a centered 1D search structure that stores a set of intervals. Each node in the tree is associated with a pivot point. The intervals are partitioned based on whether they lie entirely to the left, entirely to the right, or contain the pivot.
Interval representation:
1class IntervalNode:2 def __init__(self, pivot):3 self.pivot = pivot # Midpoint coordinate4 self.left = None # Subtree for intervals to the left5 self.right = None # Subtree for intervals to the right6 # Stored intervals containing the pivot7 self.by_low = [] # Sorted by start coordinate8 self.by_high = [] # Sorted by end coordinate
To build the tree, we choose a splitting pivot coordinate (usually the median of all start and end points) and partition the list of intervals.
Partition Rules:
1def build_interval_tree(intervals):2 if not intervals:3 return None45 # 1. Find pivot (e.g., median of endpoints)6 endpoints = []7 for start, end in intervals:8 endpoints.extend([start, end])9 pivot = sorted(endpoints)[len(endpoints) // 2]1011 # 2. Partition intervals12 left, right, center = [], [], []13 for start, end in intervals:14 if end < pivot:15 left.append((start, end))16 elif start > pivot:17 right.append((start, end))18 else:19 center.append((start, end))2021 # 3. Recursively construct subtrees22 node = IntervalNode(pivot)23 node.by_low = sorted(center, key=lambda x: x[0])24 node.by_high = sorted(center, key=lambda x: x[1])25 node.left = build_interval_tree(left)26 node.right = build_interval_tree(right)27 return node
Querying an Interval Tree with a coordinate point $x$ finds all intervals containing $x$. We recursively traverse down, pruning entire subtrees.
Query Algorithm:
1def query_interval_tree(node, x, results):2 if not node:3 return45 # Compare query coordinate with node pivot6 if x < node.pivot:7 # Traverse center sorted by start (left endpoint)8 for inv in node.by_low:9 if inv[0] > x:10 break # Stop: remaining starts are larger than x11 results.append(inv)12 # Search left subtree13 query_interval_tree(node.left, x, results)14 else:15 # Traverse center sorted by end (right endpoint) in reverse16 for inv in reversed(node.by_high):17 if inv[1] < x:18 break # Stop: remaining ends are smaller than x19 results.append(inv)20 # Search right subtree21 query_interval_tree(node.right, x, results)
Adding intervals dynamically requires checking their placement relative to each node's pivot. If an interval lies entirely left or right, we recurse down. If it intersects the pivot, we append it to the node's sorted center set.
InsertInterval(node, interval):
1def insert_interval(node, interval) -> IntervalNode:2 if node is None:3 # Create leaf with interval as initial pivot4 return IntervalNode((interval[0] + interval[1]) / 2.0)56 start, end = interval7 if end < node.pivot:8 # Add to left subtree9 node.left = insert_interval(node.left, interval)10 elif start > node.pivot:11 # Add to right subtree12 node.right = insert_interval(node.right, interval)13 else:14 # Overlaps pivot: add to center lists and sort15 node.by_low.append(interval)16 node.by_low.sort(key=lambda x: x[0])17 node.by_high.append(interval)18 node.by_high.sort(key=lambda x: x[1])1920 return node