KD树是一种查询索引结构,广泛应用于数据库索引中。从概念的角度讲,它是一种高纬数据的快速查询结构,本文首先介绍1维数据的索引查询,然后介绍2维KD树的创建和查询
2.1维数据的查询
平衡二叉树示意图
对于1维数据的查询,使用平衡二叉树建立索引即可。如果现在将查询条件变为:语文成绩介于30~93,数学成绩结余30~90,又该如何处理呢?
3.KD树
针对多维数据索引,是否也存在类似的一维的索引方法呢?先看2维数据的集合示意图:
2维数据示意图
如果先根据语文成绩,将所有人的成绩分成两半,其中一半的语文成绩<=c1,另一半的语文成绩>c1,分别得到集合S1,S2;然后针对S1,根据数学成绩分为两半,其中一半的数学成绩<=m1,另一半的数学成绩>m1,分别得到S3,S4,针对S2,根据数学成绩分为两半,其中一半的数学成绩<=m2,另一半的数学成绩>m2,分别得到S5,S6;再根据语文成绩分别对S3,S4,S5,S6继续执行类似划分得到更小的集合,然后再在更小的集合上根据数学成绩继续,...
上面描述的就是构建KD树的基本思路,其构建后的KD树如下图所示:
KD树示意图
如图所示,l1左边都是语文成绩低于45分,l1右边都是语文成绩高于45分的;l2下方都是语文成绩低于45分且数学成绩低于50分的,l2上方都是语文成绩低于45分且数学成绩高于50分的,后面以此类推。下面的图示,更清晰地表示了KD树的结构及其对应的二叉树:
KD树及对应的二叉树
在了解了KD树的基本原理后,剩下的工作就是如何构建KD树以及如何在KD树上查询了。
3.1KD树构建算法
相对而言,构建KD树是针对高维数据,需要针对每一维都进行二分,针对二维数据的KD树构建算法如下图所示:
KD树构建算法
其中的P表示待构建的元素集合,depth表示当前是KD树的第几层,如果depth是偶数,通过纵向线对集合进行划分;如果depth是奇数,通过横向线进行划分(3~5行);第6、7行递归进行KD树中子树的构建。
3.2KD树查询算法
在构建了KD树的基础上,如何进行查询其实是一个相对简单的问题了,在这里需要注意的是,在KD树中每一层划分所依据的是哪一维的数据,其它的根二叉树其实没有差别。KD树查询算法如下图所示:
查询算法
算法的v表示KD树中当前搜索的子树,R表示是一个高维数据的区域,区域的概念下图所示:
区域示意图
KD树查询递推公式
4.小结
1.2)寻找d维最小坐标值点a)若当前节点的切分维度是d因其右子树节点均大于等于当前节点在d维的坐标值,所以可以忽略其右子树,仅在其左子树进行搜索。若无左子树,当前节点即是最小坐标值节点。b)若当前节点的切分维度不是d需在其左子树与右子树分别进行递归搜索。如下为寻找d维最小坐标值点代码:
1.3)新增节点从根节点出发,若待插入节点在当前节点切分维度的坐标值小于当前节点在该维度的坐标值时,在其左子树插入;若大于等于当前节点在该维度的坐标值时,在其右子树插入。递归遍历,直至叶子节点。如下为新增节点代码:
多次新增节点可能引起树的不平衡。不平衡性超过某一阈值时,需进行再平衡。1.4)删除节点最简单的方法是将待删节点的所有子节点组成一个新的集合,然后对其进行重新构建。将构建好的子树挂载到被删节点即可。此方法性能不佳,下面考虑优化后的算法。假设待删节点T的切分维度为x,下面根据待删节点的几类不同情形进行考虑。a)无子树本身为叶子节点,直接删除。b)有右子树在T.right寻找x切分维度最小的节点p,然后替换被删节点T;递归处理删除节点p。c)无右子树有左子树在T.left寻找x切分维度最小的节点p,即p=findmin(T.left,cutting-dim=x),然后用节点p替换被删节点T;将原T.left作为p.right;递归处理删除节点p。(之所以未采用findmax(T.left,cutting-dim=x)节点来替换被删节点,是由于原被删节点的左子树节点存在x维度最大值相等的情形,这样就破坏了左子树在x分割维度的坐标需小于其根节点的定义)如下为删除节点代码:
2)最近邻搜索给定点p,查询数据集中与其距离最近点的过程即为最近邻搜索。如在上文构建好的k-dtree上搜索(3,5)的最近邻时,本文结合如下左右两图对二维空间的最近邻搜索过程作分析。a)首先从根节点(7,2)出发,将当前最近邻设为(7,2),对该k-dtree作深度优先遍历。以(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)。
如下为最近邻搜索代码:3)复杂度分析
操作
平均复杂度
最坏复杂度
新增节点
O(logn)
O(n)
删除节点
最近邻搜索
4)scikit-learn使用scikit-learn是一个实用的机器学习类库,其有KDTree的实现。如下例子为直观展示,仅构建了一个二维空间的k-dtree,然后对其作k近邻搜索及指定半径的范围搜索。多维空间的检索,调用方式与此例相差无多。