kd树

k-d树

计算机科学里,k-d树( k-维的缩写)是在k欧几里德空间组织的数据结构。k-d树可以使用在多种应用场合,如多维键值搜索(例:范围搜寻及最邻近搜索)。k-d树是空间二分树Binary space partitioning )的一种特殊情况。[1]

可以看到,KD树是基于欧式距离度量的。

简介:

k-d树是每个节点都为k维点的二叉树。所有非叶子节点可以视作用一个超平面把空间分区成两个半空间( Half-space[失效链接] )。节点左边的子树代表在超平面左边的点,节点右边的子树代表在超平面右边的点。选择超平面的方法如下:每个节点都与k维中垂直于超平面的那一维有关。因此,如果选择按照x轴划分,所有x值小于指定值的节点都会出现在左子树,所有x值大于指定值的节点都会出现在右子树。这样,超平面可以用该x值来确定,其法线为x轴的单位向量

 

KD树的算法主要分为KD树的构造和查找两部分。

KD树的构造:

方法一:

有很多种方法可以选择轴垂直分区面( axis-aligned splitting planes ),所以有很多种创建k-d树的方法。 最典型的方法如下:

  • 随着树的深度轮流选择轴当作分区面。(例如:在三维空间中根节点是 x 轴垂直分区面,其子节点皆为 y 轴垂直分区面,其孙节点皆为 z 轴垂直分区面,其曾孙节点则皆为 x 轴垂直分区面,依此类推。)
  • 点由垂直分区面之轴座标的中位数区分并放入子树

这个方法产生一个平衡的k-d树。每个叶节点的高度都十分接近。然而,平衡的树不一定对每个应用都是最佳的。[1]

这是维基百科的方法,就是轮流用各个维度来划分。

 

方法二:

 k-d树是一个二叉树,每个节点表示一个空间范围。表1给出的是k-d树每个节点中主要包含的数据结构。

表1  k-d树中每个节点的数据类型

域名数据类型描述
Node-data数据矢量数据集中某个数据点,是n维矢量(这里也就是k维)
Range空间矢量该节点所代表的空间范围
split整数垂直于分割超平面的方向轴序号
Leftk-d树由位于该节点分割超平面左子空间内所有数据点所构成的k-d树
Rightk-d树由位于该节点分割超平面右子空间内所有数据点所构成的k-d树
parentk-d树父节点

  从上面对k-d树节点的数据类型的描述可以看出构建k-d树是一个逐级展开的递归过程。表2给出的是构建k-d树的伪码。

表2  构建k-d树的伪码

算法:构建k-d树(createKDTree)
输入:数据点集Data-set和其所在的空间Range
输出:Kd,类型为k-d tree
1.If Data-set为空,则返回空的k-d tree

2.调用节点生成程序:

  (1)确定split域:对于所有描述子数据(特征矢量),统计它们在每个维上的数据方差。以SURF特征为例,描述子为64维,可计算64个方差。挑选出最大值,对应的维就是split域的值。数据方差大表明沿该坐标轴方向上的数据分散得比较开,在这个方向上进行数据分割有较好的分辨率;

  (2)确定Node-data域:数据点集Data-set按其第split域的值排序。位于正中间的那个数据点被选为Node-data。此时新的Data-set' = Data-set\Node-data(除去其中Node-data这一点)。

3.dataleft = {d属于Data-set' && d[split] ≤ Node-data[split]}

   Left_Range = {Range && dataleft}

   dataright = {d属于Data-set' && d[split] > Node-data[split]}

   Right_Range = {Range && dataright}

4.left = 由(dataleft,Left_Range)建立的k-d tree,即递归调用createKDTree(dataleft,Left_

   Range)。并设置left的parent域为Kd;

   right = 由(dataright,Right_Range)建立的k-d tree,即调用createKDTree(dataleft,Left_

   Range)。并设置right的parent域为Kd。

  以上述举的实例来看,过程如下:

  由于此例简单,数据维度只有2维,所以可以简单地给x,y两个方向轴编号为0,1,也即split={0,1}。

  (1)确定split域的首先该取的值。分别计算x,y方向上数据的方差得知x方向上的方差最大,所以split域值首先取0,也就是x轴方向;

  (2)确定Node-data的域值。根据x轴方向的值2,5,9,4,8,7排序选出中值为7,所以Node-data = (7,2)。这样,该节点的分割超平面就是通过(7,2)并垂直于split = 0(x轴)的直线x = 7;

  (3)确定左子空间和右子空间。分割超平面x = 7将整个空间分为两部分,如图2所示。x < =  7的部分为左子空间,包含3个节点{(2,3),(5,4),(4,7)};另一部分为右子空间,包含2个节点{(9,6),(8,1)}。

图2  x=7将整个空间分为两部分

  如算法所述,k-d树的构建是一个递归的过程。然后对左子空间和右子空间内的数据重复根节点的过程就可以得到下一级子节点(5,4)和(9,6)(也就是左右子空间的'根'节点),同时将空间和数据集进一步细分。如此反复直到空间中只包含一个数据点,如图1所示。最后生成的k-d树如图3所示。

图3  上述实例生成的k-d树

  注意:每一级节点旁边的'x'和'y'表示以该节点分割左右子空间时split所取的值。

该方法参考资料来自【2】

 

k-d树上的最邻近查找算法

  在k-d树中进行数据的查找也是特征匹配的重要环节,其目的是检索在k-d树中与查询点距离最近的数据点。这里先以一个简单的实例来描述最邻近查找的基本思路。

  星号表示要查询的点(2.1,3.1)。通过二叉搜索,顺着搜索路径很快就能找到最邻近的近似点,也就是叶子节点(2,3)。而找到的叶子节点并不一定就是最邻近的,最邻近肯定距离查询点更近,应该位于以查询点为圆心且通过叶子节点的圆域内。为了找到真正的最近邻,还需要进行'回溯'操作:算法沿搜索路径反向查找是否有距离查询点更近的数据点。此例中先从(7,2)点开始进行二叉查找,然后到达(5,4),最后到达(2,3),此时搜索路径中的节点为<(7,2),(5,4),(2,3)>,首先以(2,3)作为当前最近邻点,计算其到查询点(2.1,3.1)的距离为0.1414,然后回溯到其父节点(5,4),并判断在该父节点的其他子节点空间中是否有距离查询点更近的数据点。以(2.1,3.1)为圆心,以0.1414为半径画圆,如图4所示。发现该圆并不和超平面y = 4交割,因此不用进入(5,4)节点右子空间中去搜索。

图4  查找(2.1,3.1)点的两次回溯判断

  再回溯到(7,2),以(2.1,3.1)为圆心,以0.1414为半径的圆更不会与x = 7超平面交割,因此不用进入(7,2)右子空间进行查找。至此,搜索路径中的节点已经全部回溯完,结束整个搜索,返回最近邻点(2,3),最近距离为0.1414。

  一个复杂点了例子如查找点为(2,4.5)。同样先进行二叉查找,先从(7,2)查找到(5,4)节点,在进行查找时是由y = 4为分割超平面的,由于查找点为y值为4.5,因此进入右子空间查找到(4,7),形成搜索路径<(7,2),(5,4),(4,7)>,取(4,7)为当前最近邻点,计算其与目标查找点的距离为3.202。然后回溯到(5,4),计算其与查找点之间的距离为3.041。以(2,4.5)为圆心,以3.041为半径作圆,如图5所示。可见该圆和y = 4超平面交割,所以需要进入(5,4)左子空间进行查找。此时需将(2,3)节点加入搜索路径中得<(7,2),(2,3)>。回溯至(2,3)叶子节点,(2,3)距离(2,4.5)比(5,4)要近,所以最近邻点更新为(2,3),最近距离更新为1.5。回溯至(7,2),以(2,4.5)为圆心1.5为半径作圆,并不和x = 7分割超平面交割,如图6所示。至此,搜索路径回溯完。返回最近邻点(2,3),最近距离1.5。k-d树查询算法的伪代码如表3所示。

图5  查找(2,4.5)点的第一次回溯判断

图6  查找(2,4.5)点的第二次回溯判断

 

表3  标准k-d树查询算法

算法:k-d树最邻近查找

输入:Kd,    //k-d tree类型

     target  //查询数据点

输出:nearest, //最邻近数据点

     dist      //最邻近数据点和查询点间的距离

1. If Kd为NULL,则设dist为infinite并返回

2. //进行二叉查找,生成搜索路径

   Kd_point = &Kd;                   //Kd-point中保存k-d tree根节点地址

   nearest = Kd_point -> Node-data;  //初始化最近邻点

   while(Kd_point)

     push(Kd_point)到search_path中; //search_path是一个堆栈结构,存储着搜索路径节点指针

 /*** If Dist(nearest,target) > Dist(Kd_point -> Node-data,target)

       nearest  = Kd_point -> Node-data;    //更新最近邻点

       Max_dist = Dist(Kd_point,target);  //更新最近邻点与查询点间的距离  ***/

     s = Kd_point -> split;                       //确定待分割的方向

     If target[s] <= Kd_point -> Node-data[s]     //进行二叉查找

       Kd_point = Kd_point -> left;

     else

       Kd_point = Kd_point ->right;

   nearest = search_path中最后一个叶子节点; //注意:二叉搜索时不比计算选择搜索路径中的最邻近点,这部分已被注释

   Max_dist = Dist(nearest,target);    //直接取最后叶子节点作为回溯前的初始最近邻点

3. //回溯查找

   while(search_path != NULL)

     back_point = 从search_path取出一个节点指针;   //从search_path堆栈弹栈

     s = back_point -> split;                   //确定分割方向

     If Dist(target[s],back_point -> Node-data[s]) < Max_dist   //判断还需进入的子空间

       If target[s] <= back_point -> Node-data[s]

         Kd_point = back_point -> right;  //如果target位于左子空间,就应进入右子空间

       else

         Kd_point = back_point -> left;    //如果target位于右子空间,就应进入左子空间

       将Kd_point压入search_path堆栈;

     If Dist(nearest,target) > Dist(Kd_Point -> Node-data,target)

       nearest  = Kd_point -> Node-data;                 //更新最近邻点

       Min_dist = Dist(Kd_point -> Node-data,target);  //更新最近邻点与查询点间的距离

  上述两次实例表明,当查询点的邻域与分割超平面两侧空间交割时,需要查找另一侧子空间,导致检索过程复杂,效率下降。研究表明N个节点的K维k-d树搜索过程时间复杂度为:tworst=O(kN1-1/k)。

3.1. Approximate k-means (AKM)

The first method is an alteration to the original k-means algorithm. In typical k-means, the vast majority of computation time is spent on calculating nearest neighbours between the points and cluster centers. We replace this exact computation by an approximate nearest neighbor method, and use a forest of 8 randomized k-d trees [5][15][23] built over the cluster centers at the beginning of each iteration to increase speed. We use randomized k-d tree code, optimized for matching SIFT descriptors, supplied by Lowe [16]. Usually in a k-d tree, each node splits the dataset using the dimension with the highest variance for all the data points falling into that node and the value to split on is found by taking the median value along that dimension (although the mean can also be used). In the randomized version, the splitting dimension is chosen at random from among a set of the dimensions with highest variance and the split value is randomly chosen using a point close to the median. The conjunction of these trees creates an overlapping partition of the feature space and helps to mitigate quantization effects, where features which fall close to a partition boundary are assigned to an incorrect nearest neighbour. This robustness is especially important in high-dimensions, where due to the “curse of dimensionality” [22], points will be more likely to lie close to a boundary.

A new data point is assigned to the (approximately) closest cluster center as follows. Initially, each tree is descended to a leaf and the distances to the discriminating boundaries are recorded in a single priority queue for all trees [6]. Then, we iteratively choose the most promising branch from all trees and keep adding unseen nodes into the priority queue. We stop once a fixed number of tree paths have been explored. This way, we can use more trees without significantly increasing the search time.

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值