Kd树:数据只存储在叶子上vs存储在叶子和节点上

Kd tree: data stored only in leaves vs stored in leaves and nodes

本文关键字:叶子 存储 vs 节点 Kd 数据      更新时间:2023-10-16

我试图实现一个Kd树来执行最近邻和近似最近邻搜索在c++中。到目前为止,我遇到了最基本的Kd树的两个版本。

  1. 数据存储在节点和叶子中,如这里
  2. 数据仅存储在叶子中的一种,例如这里

它们似乎基本相同,具有相同的渐近性质。

我的问题是:选择一个而不是另一个是否有一些原因?

到目前为止,我认为有两个原因:

  1. 在节点中存储数据的树也较浅1层。
  2. 只在叶子中存储数据的树更容易实现delete data功能

在决定做哪一个之前,还有其他我应该考虑的原因吗?

只需将节点标记为已删除,并将任何结构更改推迟到下一次树重建。k-d树会随着时间的推移而退化,所以你需要频繁地重建树。k-d树对于没有变化的低维数据集非常有用,或者您可以轻松地重建(近似)最优树。

至于实现树,我建议使用一个简约的结构。我通常使用而不是节点。我使用了一个数据对象引用数组。轴是由当前搜索深度定义的,不需要将其存储在任何地方。左邻域和右邻域由数组的二叉搜索树给出。(否则,只需添加一个byte数组,即数据集大小的一半,用于存储您使用的轴)。加载树是由专门的快速排序完成的。理论上它是O(n^2)的最坏情况,但有一个好的启发式,如5的中间值,你可以得到O(n log n)相当可靠,并以最小的常数开销。

虽然它对C/c++没有那么大的作用,但在许多其他语言中,您将为管理大量对象付出相当大的代价。type*[]是您能找到的最便宜的数据结构,特别是它不需要大量的管理工作。要将一个元素标记为已删除,您可以将其null,并在遇到null时搜索两侧。对于插入,我首先将它们收集到缓冲区中。当修改计数器达到阈值时,重新构建。

这就是它的全部意义:如果你的树重建真的很便宜(就像使用一个几乎预先排序的数组一样便宜!),那么频繁地重建树是没有坏处的。线性扫描在一个短的"插入列表"是非常CPU缓存友好。跳过null s也很便宜。

如果你想要一个更动态的结构,我建议你看看R*-树。它们实际上是为了平衡插入和删除,并在面向磁盘的块结构中组织数据。但即使是对于r树,也有报道称保留插入缓冲区等来推迟结构变化可以提高性能。在许多情况下,散装装载也很有帮助!