Interval Trees
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
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.
Partition Rules:
end < pivot). 2. Right Subtree: All intervals completely to the right of the pivot (start > pivot). 3. Center Node: All intervals containing/overlapping the pivot (start ≤ pivot ≤ end).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
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.
Query Algorithm:
x to node pivot. 2. Pruning subtrees: If x < pivot, we only need to search the left subtree (plus the center set). If x > pivot, we only need to search the right subtree (plus the center set). 3. Early Stopping: By traversing the sorted center arrays, we stop checking as soon as an interval is guaranteed not to overlap.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)
Querying an Interval Tree with a coordinate point x finds all intervals containing x. We recursively traverse down, pruning entire subtrees.
InsertInterval(node, interval):
end < pivot, recurse left. 2. Pivot Right: If start > pivot, recurse right. 3. Sorted Insertion: If it overlaps, insert into the center set and re-sort lists by start/end points.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
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.