에듀테크 프로젝트 대시보드
k-d 트리 (K-D Tree)
"이 좌표에서 가장 가까운 점은?" — 점이 100만 개면 전부 거리를 재는 건 너무 느리다. k-d 트리는 평면을 축을 번갈아 가며 반씩 잘라 이진 트리로 만들어, 볼 필요 없는 영역을 통째로 건너뛴다.
질의점 q 하나에 대해 최근접 점을 찾으려면, 소박하게는 모든 점과의 거리를 계산해 최솟값을 고른다. 점이 N개면 질의 하나에 O(N). 지도에 매장 50만 개, 매 프레임 수천 번 질의라면 감당이 안 된다.
k-d 트리는 점들을 미리 O(N log N)에 정리해 두고, 질의를 평균 O(log N)에 답한다. 핵심 아이디어: "질의점에서 지금까지 찾은 최선 거리보다, 저 영역 경계가 더 멀면 그 영역은 볼 필요가 없다."
깊이 0에서는 x 좌표 기준으로, 깊이 1에서는 y 좌표 기준으로, 깊이 2에서 다시 x … 이렇게 차원을 돌아가며 나눈다. 각 단계에서 해당 축의 중앙값(median) 점을 노드로 삼으면 트리가 균형 잡힌다.
def build(points, depth=0): if not points: return None axis = depth % 2 # 0 = x축, 1 = y축 points.sort(key=lambda p: p[axis]) mid = len(points) // 2 # 중앙값 = 균형 return Node( point = points[mid], axis = axis, left = build(points[:mid], depth+1), # 이 축에서 더 작은 점들 right = build(points[mid+1:], depth+1), # 더 큰 점들 )
아래에서 왼쪽 평면을 클릭해 점을 추가해 보자. 파란 선은 x 분할, 주황 선은 y 분할이다. 오른쪽에 대응하는 이진 트리가 함께 그려진다.
위 데모에서 「최근접 탐색 모드」를 켜고 평면을 클릭하면 질의점 q가 놓이고, 탐색 과정이 로그에 단계별로 찍힌다. 알고리즘은 이렇게 움직인다.
q가 노드보다 작으면 왼쪽, 크면 오른쪽으로 내려간다. 리프에 닿으면 그 점을 일단 "현재 최선(best)"으로 잡는다.def nearest(node, q, best): if node is None: return best d = dist(node.point, q) if d < best.dist: best = (node.point, d) axis = node.axis diff = q[axis] - node.point[axis] near, far = (node.left, node.right) if diff < 0 else (node.right, node.left) best = nearest(near, q, best) # 1) 가까운 쪽 먼저 if abs(diff) < best.dist: # 2) 분할선이 best보다 가까울 때만 best = nearest(far, q, best) # 반대편도 확인 return best
|diff| < best.dist 가 이 자료구조의 전부다. diff 는 질의점과 분할선 사이의 축 방향 거리. 이것이 이미 찾은 최근접 거리보다 크면, 분할선 너머의 어떤 점도 그보다 가까울 수 없다(피타고라스). 운이 좋으면 트리의 절반 이상을 안 본다.
탐색 모드에서 질의를 여러 번 해보면, 방문 노드 수가 브루트 포스(전체 점 수) 보다 훨씬 작은 걸 볼 수 있다. 점을 30개 정도 뿌리고 시도해 보자. 다만 질의점이 점 구름에서 멀리 떨어져 있거나 점이 몰려 있으면 가지치기가 덜 되어 방문 수가 늘어난다.
| 연산 | 평균 | 최악 |
|---|---|---|
| 트리 만들기 (중앙값 분할) | O(N log N) | O(N log² N) |
| 최근접 이웃 1건 | O(log N) | O(N) |
| 점 삽입 / 삭제 | O(log N) | O(N) (불균형 시) |
| 범위 검색 (사각형 안 점들) | O(√N + k) | O(N) |
k가 커지면(대략 k > 10~20) 가지치기가 거의 안 먹혀 브루트 포스와 비슷해진다. 고차원에서는 근사 방법(Ball tree, HNSW, LSH)을 쓴다.한 번에 한 축만, 점 위치(중앙값)에서 분할. 이진 트리. 점 개수에 자동 적응. 차원 확장이 자연스러움(k차원).
공간을 정확히 4등분(가운데에서 x·y 동시에). 4갈래 트리. 점이 몰리면 깊어짐. 이미지·지형처럼 균일 격자에 강함.