算法概述
八叉树(Octree)是四叉树在三维空间的自然扩展,广泛应用于计算机图形学、物理引擎碰撞检测、三维地理信息系统和点云处理等领域。其核心思想是将三维空间递归地划分为8个子空间(象限),每个节点代表一个立方体区域,根据空间内点的分布决定是否继续细分,从而在密集区域提供高精度、稀疏区域保持低消耗的适应性空间索引。
核心原理
八叉树的构建过程:
- 定义根节点覆盖整个三维空间边界框(AABB)
- 将点插入对应节点,若节点内点数超过阈值且未达到最大深度,则分裂为8个子节点
- 8个子节点分别对应父节点的:左下前、右下前、左下后、右下后、左上前、右上前、左上后、右上后
- 重复步骤2-3直至满足终止条件
空间查询时,通过递归判断查询区域与节点包围盒的交集关系,快速排除无关分支。
Java完整实现
import java.util.ArrayList;
import java.util.List;
/**
* 八叉树三维空间索引实现
* 应用场景:点云处理、碰撞检测、最近邻搜索、三维GIS
*/
public class Octree {
// 三维点
public static class Point3D {
public double x, y, z;
public Object data; // 可附加任意数据
public Point3D(double x, double y, double z) {
this.x = x; this.y = y; this.z = z;
}
public Point3D(double x, double y, double z, Object data) {
this(x, y, z);
this.data = data;
}
public double distanceSquared(Point3D other) {
double dx = x - other.x;
double dy = y - other.y;
double dz = z - other.z;
return dx * dx + dy * dy + dz * dz;
}
@Override
public String toString() {
return String.format("(%.2f, %.2f, %.2f)", x, y, z);
}
}
// 轴对齐包围盒
public static class BoundingBox {
public double minX, minY, minZ;
public double maxX, maxY, maxZ;
public BoundingBox(double minX, double minY, double minZ,
double maxX, double maxY, double maxZ) {
this.minX = minX; this.minY = minY; this.minZ = minZ;
this.maxX = maxX; this.maxY = maxY; this.maxZ = maxZ;
}
public double getCenterX() { return (minX + maxX) / 2; }
public double getCenterY() { return (minY + maxY) / 2; }
public double getCenterZ() { return (minZ + maxZ) / 2; }
public boolean contains(Point3D p) {
return p.x >= minX && p.x <= maxX &&
p.y >= minY && p.y <= maxY &&
p.z >= minZ && p.z <= maxZ;
}
public boolean intersects(BoundingBox other) {
return !(other.minX > maxX || other.maxX < minX ||
other.minY > maxY || other.maxY < minY ||
other.minZ > maxZ || other.maxZ < minZ);
}
// 计算点到包围盒的最小距离平方
public double distanceSquaredToPoint(Point3D p) {
double dx = 0, dy = 0, dz = 0;
if (p.x < minX) dx = minX - p.x;
else if (p.x > maxX) dx = p.x - maxX;
if (p.y < minY) dy = minY - p.y;
else if (p.y > maxY) dy = p.y - maxY;
if (p.z < minZ) dz = minZ - p.z;
else if (p.z > maxZ) dz = p.z - maxZ;
return dx * dx + dy * dy + dz * dz;
}
}
private static final int MAX_POINTS = 8; // 节点最大点数阈值
private static final int MAX_DEPTH = 10; // 最大递归深度
private BoundingBox bounds;
private List<Point3D> points;
private Octree[] children; // 8个子节点
private int depth;
public Octree(BoundingBox bounds, int depth) {
this.bounds = bounds;
this.points = new ArrayList<>();
this.children = null;
this.depth = depth;
}
/**
* 插入点到八叉树
*/
public void insert(Point3D point) {
if (!bounds.contains(point)) {
return; // 点不在当前节点范围内
}
if (children == null) {
points.add(point);
// 超过阈值且未达到最大深度时分裂
if (points.size() > MAX_POINTS && depth < MAX_DEPTH) {
subdivide();
}
} else {
// 已分裂,插入到对应子节点
for (Octree child : children) {
if (child.bounds.contains(point)) {
child.insert(point);
break;
}
}
}
}
/**
* 将当前节点分裂为8个子节点
*/
private void subdivide() {
children = new Octree[8];
double cx = bounds.getCenterX();
double cy = bounds.getCenterY();
double cz = bounds.getCenterZ();
// 8个子象限的包围盒
children[0] = new Octree(new BoundingBox(bounds.minX, bounds.minY, bounds.minZ, cx, cy, cz), depth + 1);
children[1] = new Octree(new BoundingBox(cx, bounds.minY, bounds.minZ, bounds.maxX, cy, cz), depth + 1);
children[2] = new Octree(new BoundingBox(bounds.minX, cy, bounds.minZ, cx, bounds.maxY, cz), depth + 1);
children[3] = new Octree(new BoundingBox(cx, cy, bounds.minZ, bounds.maxX, bounds.maxY, cz), depth + 1);
children[4] = new Octree(new BoundingBox(bounds.minX, bounds.minY, cz, cx, cy, bounds.maxZ), depth + 1);
children[5] = new Octree(new BoundingBox(cx, bounds.minY, cz, bounds.maxX, cy, bounds.maxZ), depth + 1);
children[6] = new Octree(new BoundingBox(bounds.minX, cy, cz, cx, bounds.maxY, bounds.maxZ), depth + 1);
children[7] = new Octree(new BoundingBox(cx, cy, cz, bounds.maxX, bounds.maxY, bounds.maxZ), depth + 1);
// 将已有 points 重新分配到子节点
for (Point3D p : points) {
for (Octree child : children) {
if (child.bounds.contains(p)) {
child.insert(p);
break;
}
}
}
points.clear(); // 清空当前节点的直接存储
}
/**
* 范围查询:查找指定包围盒内的所有点
*/
public List<Point3D> queryRange(BoundingBox range) {
List<Point3D> result = new ArrayList<>();
if (!bounds.intersects(range)) {
return result;
}
// 检查当前节点直接存储的点
for (Point3D p : points) {
if (range.contains(p)) {
result.add(p);
}
}
// 递归查询子节点
if (children != null) {
for (Octree child : children) {
result.addAll(child.queryRange(range));
}
}
return result;
}
/**
* 最近邻搜索:查找距离目标点最近的点
*/
public Point3D nearestNeighbor(Point3D target) {
return nearestNeighbor(target, null, Double.MAX_VALUE);
}
private Point3D nearestNeighbor(Point3D target, Point3D best, double bestDistSq) {
// 剪枝:如果当前节点的包围盒距离目标点比当前最优距离还远,直接返回
double nodeDistSq = bounds.distanceSquaredToPoint(target);
if (nodeDistSq >= bestDistSq) {
return best;
}
// 检查当前节点直接存储的点
for (Point3D p : points) {
double distSq = p.distanceSquared(target);
if (distSq < bestDistSq && distSq > 0) { // distSq > 0 排除目标点自身
bestDistSq = distSq;
best = p;
}
}
// 递归查询子节点,按距离排序优先访问更近的
if (children != null) {
// 优先访问包含目标点的子节点
for (Octree child : children) {
if (child.bounds.contains(target)) {
best = child.nearestNeighbor(target, best, bestDistSq);
if (best != null) bestDistSq = best.distanceSquared(target);
break;
}
}
// 再访问其他可能包含更近点的子节点
for (Octree child : children) {
if (!child.bounds.contains(target)) {
double childDistSq = child.bounds.distanceSquaredToPoint(target);
if (childDistSq < bestDistSq) {
best = child.nearestNeighbor(target, best, bestDistSq);
if (best != null) bestDistSq = best.distanceSquared(target);
}
}
}
}
return best;
}
public static void main(String[] args) {
// 创建八叉树,覆盖 0~100 的三维空间
Octree octree = new Octree(new BoundingBox(0, 0, 0, 100, 100, 100), 0);
// 插入随机点
java.util.Random rand = new java.util.Random(42);
for (int i = 0; i < 1000; i++) {
octree.insert(new Point3D(
rand.nextDouble() * 100,
rand.nextDouble() * 100,
rand.nextDouble() * 100,
"Point_" + i
));
}
// 范围查询
BoundingBox queryBox = new BoundingBox(40, 40, 40, 60, 60, 60);
List<Point3D> inRange = octree.queryRange(queryBox);
System.out.println("范围 " + queryBox + " 内找到 " + inRange.size() + " 个点");
// 最近邻搜索
Point3D target = new Point3D(50, 50, 50);
Point3D nearest = octree.nearestNeighbor(target);
System.out.println("距离 " + target + " 最近的点: " + nearest);
if (nearest != null) {
System.out.println("距离: " + Math.sqrt(target.distanceSquared(nearest)));
}
}
}
复杂度分析
| 操作 | 平均时间 | 最坏时间 | 空间复杂度 |
|---|---|---|---|
| 构建(N个点) | O(N log N) | O(N·D) | O(N·D) |
| 插入单个点 | O(D) | O(D) | – |
| 范围查询 | O(k + log N) | O(N) | O(k) |
| 最近邻搜索 | O(log N) | O(N) | O(D) |
其中N为点数,D为树的最大深度,k为查询结果数量。
应用场景
- 点云处理:LiDAR数据的快速邻域查询和降采样
- 物理引擎:刚体碰撞检测的粗筛阶段
- 三维GIS:地理空间数据的层级管理与LOD渲染
- 分子模拟:粒子间相互作用力的近邻列表构建
- 游戏开发:大型场景中的视锥剔除与对象管理
八叉树通过自适应的空间细分,在三维数据处理中提供了时间与空间的最优平衡。