Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

After this page, you should be able to:

Builds on: PR construction, exact lookup and range search, plus the shared box-distance helper. These functions take an existing PRQuadTree.

Code languages: Python defines the explanations, figures and self-checks. Use the language guide for native conventions and dependencies. Complete runnable topic examples: Python · JavaScript · Julia.

JavaScript points are arrays compared by same_point; Julia points are value tuples. Julia coordinate/child indices are one-based (x=1, y=2; SW/SE/NW/NE=1/2/3/4), while depth still starts at zero. R-tree orientation uses exact big-integer arithmetic for the supplied integer-coordinate geometry; nearest distances remain floating calculations. General R-tree update code is still conceptual in every language.

Query outside the root, too

Insertion must respect the root’s half-open extent. A nearest query can be anywhere: a point outside the root can still have a nearest stored point inside. Use cell distance to prioritise children and exact point distance to update the answer.

Python
JavaScript
Julia
def pr_nearest(tree, query):
    best, best_d2 = None, float("inf")
    stack = [tree.root]
    while stack:
        node = stack.pop()
        if box_distance2(query, node.bounds) > best_d2:
            continue
        if node.children is None:
            for point in node.points:
                d2 = sum((point[i] - query[i]) ** 2 for i in range(2))
                if best is None or d2 < best_d2:
                    best, best_d2 = point, d2
        else:
            stack.extend(sorted(node.children,
                                key=lambda child: box_distance2(
                                    query, child.bounds), reverse=True))
    return best

An empty leaf cannot improve the candidate; an overflow bucket must test all its points. Return None for an empty tree and one first-encountered nearest point on ties. The closure-based bound is safe even when the nearest boundary belongs to a different half-open cell. For the shared data, Q=(12,2) returns G=(9,2), squared distance 9, although Q is outside [0,12)².

Remove from the owning bucket, then merge upwards

Keep the descent path. After removal, if a parent has four leaf children whose combined occupancy is at most the bucket target, replace them with one leaf bucket. Repeat towards the root. If a parent cannot merge, its ancestors cannot yet have four leaf children along this path either.

Python
JavaScript
Julia
def pr_delete(tree, point):
    if point not in tree.members:
        return False
    node, path = tree.root, []
    while node.children is not None:
        path.append(node)
        pivot = (node.children[0].bounds[2], node.children[0].bounds[3])
        node = node.children[point_quadrant(point, pivot)]
    node.points.remove(point)
    tree.members.remove(point)
    for parent in reversed(path):
        children = parent.children
        if any(child.children is not None for child in children):
            break
        total = sum(len(child.points) for child in children)
        if total > tree.capacity:
            break
        parent.points = [p for child in children for p in child.points]
        parent.children = None
    return True

Return True only if a point was removed; the same point can be inserted again afterwards. The last deletion leaves an empty root leaf with its original extent. Keep members and the buckets consistent by using the public insertion and deletion functions rather than editing node fields.

Deleting B removes its bucket record and merges the NW sibling leaves into one bucket holding E; the root's midpoint split stays fixed.

On the shared capacity-one tree, removing B=(2,7) merges the four leaves of the root’s NW child into a leaf containing E. The root still has other internal children, so merging stops there. Neither deleting B nor the merge moves the root’s x=6 or y=6 boundaries.

Include empty cells and overflow buckets

Use height H, target bucket size b, actual terminal occupancy k, I internal nodes, V examined cells, P tested points, r range results, and S total splits during construction.

OperationTimeExtra working space, excluding output
Build n points by insertionO(n(H+1)+(b+1)S), using final height HO((b+1)(H+1)) pending work
One insertion with s new splitsO(H+1+(b+1)s), expected/amortised set/list operationsO((b+1)(H+1))
Spatial exact lookupO(H+k+1)O(1)
Range reportingO(V+P+1), includes emitting r≤P resultsO(H+1), plus O(r) output
Nearest queryO(V+P+1)O(H+1)
Delete and mergeO((b+1)(H+1)+k), expected set operationsO(H+b+1)

Deletion scans the terminal list, then checks four children per level and copies at most b records at each successful merge. Lookup and deletion can scan a large overflow bucket. Nearest/range worst cases examine all O(I+1) cells and n points. Persistent storage is O(n+I+1): explicit children include empty leaves and members holds n tuples. Here S=I when starting empty and only inserting; deleting and reinserting can create further splits over time.

Depth limits bound H but do not by themselves make k small or space O(n). With one long cluster path, many empty siblings are still allocated. No distribution-independent O(log n) nearest guarantee follows from fixed midpoint splitting.

Check your understanding

1. Must a nearest query at (12,2) be rejected by the shared PR tree?

Show explanation

No. That coordinate is forbidden for insertion into the half-open extent, but querying distances from it is valid. G=(9,2) is the nearest shared point.

2. Four leaf children contain 0,1,0,1 points, with capacity 1. Can they merge?

Show explanation

No. The total is 2, exceeding the target. Empty siblings alone do not justify a merge.

Next: R-tree representation, where bounding groups can overlap.