K维检索树类似于二叉检索树,不同的是,它能同时提供多维度属性的检索。K维(从0开始计维数)检索树的定义: K维检索树是一个n层的二叉树(根节点为第0层,依次往下为第1,2..n-1层),对于树中第n层的每个节点,其左子树所有节点的第n%k维属性小于或等于该节点的第n%k维属性;其右子树所有节点的第n%k维属性都大于或等于该节点的第n%k维属性。通俗的讲,就是将整个树的层数循环标记为0到k-1,在第0层比较第0为属性,在第1层比较第1层属性,依此类推。
假设A-G代表二维空间的点(由x,y二维坐标标示),则依次插入A-G节点到树中会构成如下所示的树。
插入A | 树为空,A作为根节点 |
插入B | 与A比较第0维属性,进入左子树,左子树为空,直接插入 |
插入C | 与A比较第0维属性,进入右子树,右子树为空,直接插入 |
插入D | 与A比较第0维属性,进入左子树,与B比较第1维属性,进入左子树… |
插入E | 与A比较第0维属性,进入左子树,与B比较第1维属性,进入右子树… |
插入F | 与A比较第0维属性,进入右子树,与C比较第1维属性,进入右子树… |
插入G | 与A比较第0维属性,进入左子树,与B比较第1维属性,进入左子树,与D比较第0维属性,进入左子树… |
算法时间复杂度:
查找:从根节点逐层比较,直到遇到目标或者子树为空,时间复杂度为O(logN)。
插入:从根节点逐层比较,按照定义的规则直到遇到叶子节点,时间复杂度为O(logN)。
删除:对于叶子节点,或只有单分支子树的节点,直接删除,否则找到右子树的最左节点或左子树的最右节点,替换掉被删节点,算法复杂度O(logN)。
优化:将树优化为平衡树,将树的节点导出至vector中,并清空树,将vector集合按第一维属性,分成两堆,左边比中间元素小,右边比中间元素大(nth-element),将中间元素插入到树中,递归将左右两堆的元素插入到左子树、右子树(比较时根据层数比较对应的维度值)。优化的时间复杂度为O(NlogN)。
区间查找:从根节点起,检查根节点是否符合条件,如果符合加入至结果集中;如果左子树跟目标区间有交集,递归查找左子树;如果右子树跟目标区间有交集,查找右子树。(设定一个动态区间,初始时与目标区间相同,进入左子树,则要更新动态区间对应维度的上限;若进入右子树,则要更新动态区间对应维度的上限;通过判断动态和目标区间是否有交集可以看出需要进入左右子树,从而减少查找空间)。
更详细的了解参考论文:Multidimensional Binary Search Trees Used for Associative Searching
libkdtree++是一个C++实现的kdtree lib,提供了insert、erase、optimise、find_exact、
find_within_range、find_nearest等接口。
find_with_range(val, range, OutputIterator)不能满足需求,因只能指定每维属性在
val[k] – range到val[k] + range的范围内。因为需要支持任意区间查找,而kdtree++没有提供
需要的接口,我在kdtree的实现中增加了find_within_range(vmin, vmax, OutputIterator)接口,
同时需要给Region添加一个构造函数。