Bentley 于 1975 年在 中提出一种多维二叉查找树,即 kd-tree,其中k代表查找空间的维度。kd-tree 可将一个给定的空间根据其存储的记录进行多级分割,每一级轮换使用各个维度并抽取一个记录在该维度上的值将空间或子空间一分为二。

比如 2d-tree,第一级抽取一个记录的 x 值按 x 维度分割,得到两个子空间,接着对两个子空间分别抽取其它记录的 y 值按 y 维度分割,再到下一级时又按 x 维度分割,最终每个分得的子空间最多包含一个记录。查找记录时从根节点开始,每级只采取一个相应的维度值进行比较,然后进入子级继续比较,如此可以快速过滤无关记录。

一.kd-tree 的构建及记录的插入

  1. 建立根节点,取出1r 并作为根节点中存储的记录;
  2. 取出r2访问根节点,根节点中已存储记录r1 ,则以r1 的 x 坐标值为横向分割面将根节点分为左右两部分建立两个子树节点,r2 的 x 坐标值大于r1 ,因此将r2存储在右节点;
  3. 取出r3 ,访问根节点,r3 的 x 坐标值大于r1 ,因此访问右子树,而右子树已存储2r ,又因其父节点是按 x 坐标横向分割的,因此r2 所在子树将以r2 的 y 坐标值纵向分割为两个子树节点,r3的 y 坐标值大于r2r ,因此将r3存储在右节点;
  4. 取出r4 ,经根节点与r1 的 x 坐标值比较进入右子树,与r2的 y 坐标值比较进入右子树到达r3 所在子树,则r3 所在子树以r3 的 x 坐标值横向分割为两子树节点r4的 x坐标值大于r3 ,则r3存储在右节点。
  5. 取出r5经根节点与r1 x 坐标值比较进入左子树,左子树为空,则直接将r5子树,无需再此分割

其时间复杂度同样为 Ologn

二.结构优化

由于 kd-tree 的构造形态跟插入记录的先后顺序有关,因此构造所得的树结构不一定是平衡的,图 所示的树结构即为不平衡二叉树。为了获取更稳定的查找速度,可以对树结构进行平衡化。但结构优化的时间复杂度很高,故仅适用于静态记录集。

将 kd-tree 用于游戏场景的分割时,不会像原始 kd-tree 那样,每个树节点都能存储对象,而是仅仅在叶节点存储对象。同时,为了避免树的层次太深,造成遍历路径过长影响效率的问题,叶节点中并不是仅能包含一个对象,而是给定一个阀值,限定一个叶节点所能包含的最多对象数量。同样为了树层次,必须尽量构造平衡树,因此在向树中插入节点时,并不是随机一个一个对象插入的,而是先将所有对象按某一维度排序,取一个中间值,使得小于该值的对象数量与大于该值的对象数量相当,然后将当前空间分割成两个子空间,中间值作为分割维度值,小于分割维度值的对象属于左子树,大于分割维度值的对象属于右子树。

然后对两个子空间递归进行相同的分割,只不过分割维度要轮换使用(上一级用 X ,下一级就要用 Y ),当某个分割得到的子空间包含的对象数量小于一个阀值时,就不再对这个子空间进行分割了,如此,一个叶节点便生成了,它包含着一定数量的对象,如此将整个空间进行分割,所有对象被均匀的放置在各个叶节点中。另外,分割场景的目的在于快速排除无关区域中的对象,若 AOI 尺寸大于分割区域,即 AOI 可同时跨越 4 个以上区域,则分割在一定程度上将失去意义 ,因此,若某个子空间横向或纵向小于 2 倍的 AOI 边长时,无论该子空间包含多少对象也不再进行分割。

KD-tree 也是一颗二叉树,只是每个结点内包含的元素都为 K 维点,这样的 KD-tree为特殊的二叉树。如图 所示,其左边为场景的划分,右边则是场景的二维 KD-tree。从图中我们可以看出在构造一个物体的 KD-tree 的关键在于各个轴划剖分位置的确定,常用的划分为中分,但是中分对于随机性很强的场景并不适用,它会造成空间中所得到的左右结点所包含的面片数分布极其不均匀,对于那些场景面片高度随机的几何场景,会产生质量极低的KD-tree。为了提高效率,研究者提出基于SAH划分的KD-tree,其中 SAH 全称 Surface Area Heuristic,表面积启发剖分。