A vantage-point tree chooses a pivot object, measures every remaining object's distance to it, and divides the objects around a median radius. This static index uses Manhattan distance between depot coordinates, so the distance function satisfies the triangle inequality. Its near partition has distances no greater than the stored radius; its far partition has distances no less. A nearest-neighbor query evaluates the pivot, searches the side containing the query first, then visits the other side only if the best distance still reaches the radius boundary. Equal-distance candidates use depot ID as a deterministic tie. Splitting by median position keeps the shape balanced even when several points share a radius.
Vantage-point trees: nearest depots by a metric radius
Operational case
Five depot coordinates include central at (47,47) and west at (83,29). For destination (50,45), central is five Manhattan units away; for (80,30), west is four. The pivot chosen during construction is not guaranteed to be the nearest. If a query lies just inside a pivot radius, a qualifying depot may live in the far child; pruning it without comparing the current best distance to that radius would return the wrong result. This representation is frozen. Moving a depot changes the distance partitions and requires a rebuild, whereas a moving-point grid elsewhere in the curriculum supports live coordinate updates.
Working Python program
def travel_metric(first, second):
return abs(first[0] - second[0]) + abs(first[1] - second[1])
class DepotVantageTree:
def __init__(self, depots):
if len({depot_id for depot_id, _ in depots}) != len(depots):
raise ValueError("depot IDs must be unique")
self.root = self._build(list(depots))
def _build(self, depots):
if not depots:
return None
pivot_id, pivot_point = depots[0]
remaining = sorted(((travel_metric(pivot_point, point), depot_id, point)
for depot_id, point in depots[1:]))
middle = len(remaining) // 2
radius = remaining[middle][0] if remaining else 0
return {"id": pivot_id, "point": pivot_point, "radius": radius,
"near": self._build([(depot_id, point) for _, depot_id, point in remaining[:middle]]),
"far": self._build([(depot_id, point) for _, depot_id, point in remaining[middle:]])}
def nearest(self, destination):
best = None
def visit(node):
nonlocal best
if node is None:
return
distance = travel_metric(destination, node["point"])
candidate = (distance, node["id"])
if best is None or candidate < best:
best = candidate
near_first = distance < node["radius"]
first = node["near"] if near_first else node["far"]
second = node["far"] if near_first else node["near"]
visit(first)
if second is not None and abs(distance - node["radius"]) <= best[0]:
visit(second)
visit(self.root)
return None if best is None else (best[1], best[0])
if __name__ == "__main__":
depots = DepotVantageTree([
("east", (19, 47)), ("west", (83, 29)), ("north", (47, 61)),
("south", (29, 19)), ("central", (47, 47)),
])
print("nearest to (50,45):", depots.nearest((50, 45)))
print("nearest to (80,30):", depots.nearest((80, 30)))Output
nearest to (50,45): ('central', 5)
nearest to (80,30): ('west', 4)Time, space, and tradeoff
Sorting distances in each balanced component gives O(N log-squared N) construction time in this direct implementation and O(N) stored nodes. A nearest query can visit O(N) pivots in a difficult metric distribution or high dimension; it uses O(H) recursion space for tree height H. No unconditional logarithmic nearest-neighbor bound is claimed. The metric itself can be expensive: with long vectors or string edit distance, computing distance may dominate the tree walk. A k-d tree uses coordinate axis cuts, while this pivot split depends only on the metric and can be reused for non-Cartesian objects with an appropriate distance function.
Common Mistakes
- Do not prune the opposite child when the best-distance ball reaches the radius boundary.
- Do not use a distance function that violates the triangle inequality and expect exact pruning.
- Do not promise logarithmic nearest queries for every data distribution.
- Do not update a depot coordinate without rebuilding the static partitions.
Connected lessons
- Trees and Heaps
- Data Structures
- K-d trees: exact nearest depot with plane pruning
- Spatial hash grids: move points between occupied cells
- Bounding-volume hierarchies: prune box overlap searches
- Projects
- Quizzes
Compare its update and query contract with BK-trees: search incident labels within edit distance, Centroid decomposition: nearest marked depot on a fixed tree, Two-level perfect hashing: exact static case membership, then complete the structure audit and decision quiz.
Ball trees: prune exact nearest-depot search with radius bounds examines a related structure with a different operation boundary.
