k-d树 [1] (k-dimensional树的简称),是一种分割k维数据空间的数据结构。主要应用于多维空间关键数据的搜索(如:范围搜索和最近邻搜索)。K-D树是二进制空间分割树的特殊的情况。
四叉树,八叉树都是只能用于二维,三维的, 而且是空间等分的,
其使用比较困难的是最小粒度(叶节点)的确定,粒度较大时,有的节点数据量可能仍比较大,后续查询效率仍比较低,反之,粒度较小,八叉树的深度增加,需要的内存空间也比较大(每个非叶子节点需要八个指针),效率也降低。而等分的划分依据,使得在数据重心有偏斜的情况下,受划分深度限制,其效率不是太高。
kdtree 可以用于n维,虽然只有2叉, 但是不是空间等分的.
k-d在邻域查找上比较有优势,但在大数据量的情况下,若划分粒度较小时,建树的开销也较大,但比八叉树灵活些。在小数据量的情况下,其搜索效率比较高,但在数据量增大的情况下,其效率会有一定的下降,一般是线性上升的规律。
也有将八叉树和k-d树结合起来的应用,应用八叉树进行大粒度的划分和查找,而后使用k-d树进行细分,效率会有一定的提升,但其搜索效率变化也与数据量的变化有一个线性关系。
在介绍Kd-Tree之前,首先介绍下它的父系结构——BST(Binary Search Tree,BST)。二叉查找树,是一种具有如下性质的二叉树:
1)若它的左子树不为空。则左子树上全部结点的值均小于它的根结点的值;
2)若它的右子树不为空,则右子树上全部结点的值均大于它的根结点的值;
3)它的左、右子树也分别为二叉排序树;
我们想要查询某个数据是否位于该数据集合中时。仅仅须要将查询数据与结点值进行比較然后选择相应的子树继续往下查找就可以,查找的平均时间复杂度为:O(logN),最坏的情况下是O(N)。
如果要处理的数据是k维的, 就是kd-tree
如何构建
对于Kd-tree这样一棵二叉树,我们首先须要确定如何划分左子树和右子树
1.每次对子空间的划分时。如何确定在哪个维度上进行划分;
选择分布比较散的, 数学上讲就是方差比较大的.
统计中的方差(样本方差)是每个样本值与全体样本值的平均数之差的平方值的平均数
2.在某个维度上进行划分时,如何确保在这一维度上的划分得到的两个子集合的数量尽量相等
选择数组中的中间的数, 左边的比他小, 右边的比他大, 参考bst
例
以二维平面点(x,y)的集合(2,3),(5,4),(9,6),(4,7),(8,1),(7,2)为例
a)构建根节点时,x维度上面的方差较大,如上点集合在x维从小到大排序为(2,3),(4,7),(5,4),(7,2),(8,1),(9,6);其中值为(7,2)。(注:2,4,5,7,8,9在数学中的中值为(5 + 7)/2=6,但因该算法的中值需在点集合之内,所以本文中值计算用的是len(points)//2=3, points[3]=(7,2))
b)(2,3),(4,7),(5,4)挂在(7,2)节点的左子树,(8,1),(9,6)挂在(7,2)节点的右子树。
c)构建(7,2)节点的左子树时,点集合(2,3),(4,7),(5,4)此时的切分维度为y,中值为(5,4)作为分割平面,(2,3)挂在其左子树,(4,7)挂在其右子树。
d)构建(7,2)节点的右子树时,点集合(8,1),(9,6)此时的切分维度也为y,中值为(9,6)作为分割平面,(8,1)挂在其左子树。至此k-d tree构建完成。
上述的构建过程结合下图可以看出,构建一个k-d tree即是将一个二维平面逐步划分的过程。
最近邻搜索
如在上文构建好的k-d tree上搜索(3,5)的最近邻时,本文结合如下左右两图对二维空间的最近邻搜索过程作分析。
a)首先从根节点(7,2)出发,将当前最近邻设为(7,2),对该k-d tree作深度优先遍历。以(3,5)为圆心,其到(7,2)的距离为半径画圆(多维空间为超球面),可以看出(8,1)右侧的区域与该圆不相交,所以(8,1)的右子树全部忽略。
b)接着走到(7,2)左子树根节点(5,4),与原最近邻对比距离后,更新当前最近邻为(5,4)。以(3,5)为圆心,其到(5,4)的距离为半径画圆,发现(7,2)右侧的区域与该圆不相交,忽略该侧所有节点,这样(7,2)的整个右子树被标记为已忽略。
c)遍历完(5,4)的左右叶子节点,发现与当前最优距离相等,不更新最近邻。所以(3,5)的最近邻为(5,4)。
步骤:
(1)将查询数据Q从根结点開始,依照Q与各个结点的比較结果向下訪问Kd-Tree,直至达到叶子结点。
当中Q与结点的比較指的是将Q相应于结点中的k维度上的值与m进行比較,若Q(k) < m,则訪问左子树。否则訪问右子树。达到叶子结点时,计算Q与叶子结点上保存的数据之间的距离。记录下最小距离相应的数据点。记为当前“近期邻点”Pcur和最小距离Dcur。
(2)进行回溯(Backtracking)操作,该操作是为了找到离Q更近的“近期邻点”。
即推断未被訪问过的分支里是否还有离Q更近的点。它们之间的距离小于Dcur。
假设Q与其父结点下的未被訪问过的分支之间的距离小于Dcur,则觉得该分支中存在离P更近的数据,进入该结点,进行(1)步骤一样的查找过程,假设找到更近的数据点,则更新为当前的“近期邻点”Pcur,并更新Dcur。
假设Q与其父结点下的未被訪问过的分支之间的距离大于Dcur,则说明该分支内不存在与Q更近的点。
回溯的推断过程是从下往上进行的,直到回溯到根结点时已经不存在与P更近的分支为止。
Unity下的实现可参考
https://github.com/viliwonka/KDTree
参考:
https://blog.csdn.net/pengpengblog/article/details/76649087
https://www.cnblogs.com/zfyouxi/p/4795584.html
https://leileiluoluo.com/posts/kdtree-algorithm-and-implementation.html